暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一文讲解C语言的文件流IO

海人为记 2021-12-14
1993

I/O 操作是每个编程语言不可或缺的功能,用于不同设备之间的字节进行移进移出,这种字节流动的方式被称为流 stream

流被分为文本流 text stream
和二进制流 binary stream
两种。

  • 文本流,指的是由文本行组成的序列,在不同系统中可能不同。如文本行的最大长度和结束方式。
  • 二进制流,指的是未处理的字节序列,不作修改,可直接传送。

文件

文件 file
通常是磁盘或硬盘上的一段已命名的存储区。如 C 语言定义了结构体 FILE
,用于抽象的表示文件,并且也提供了对应的 I/O 函数来操作 FILE
结构,执行与文件相关的任务,其步骤如下所示。

  1. 打开流,程序通过特定的函数打开一个流,并指定其访问方式,如读、写或可读写。
  2. 操纵流,对打开的流做读写等操作。
  3. 关闭流,通过特定函数关闭流。

打开流

库函数中提供了 fopen
函数来打开文件,并将一个流与该文件相关联,原型如下所示。

FILE *fopen(const char *filename, const char *mode);

  • filename
    :想打开的文件或设备的名称。
  • mode
    :指的是打开后的流的访问方式。

mode
模式中的文本流以 r
w
a
来表示打开流用于读取、写入还是添加的访问方式;二进制流以 rb
wb
ab
来表示。

fopen
打开成功时,会返回一个指向 FILE
结构的指针;失败时,会返回 NULL
指针。这里必须要检查 fopen
函数的返回值,成功才可以对 FILE
指针进行后续的操作。

FILE *stream;

stream = fopen("ioexample.txt""r");
if (stream == NULL) {
    // error handling
}

关闭流

当使用 fopen
打开流并使用完后,需要关闭时,可以使用 fclose
函数来关闭,原型如下所示。

int fclose(FILE *file);

fclose
函数传入的 file
值,为要关闭的流,并且在关闭之前会刷新缓冲区,成功关闭时,函数返回值为 0
;关闭出错的话会返回 EOF

int result = fclose(stream);

可以判断 result
的结果,来判断是否正常关闭。

操纵流

当流被打开后,需要操纵流,来完成需要的操作。C 语言提供了操纵字符、字符串和二进制的 I/O 函数来操纵流,如下所示。

字符 I/O

库函数中提供了最简单的字符 I/O,用于输入和输出,获取字符的函数的原型如下所示。

int fgetc(FILE *file);

fgetc
函数会通过 file
参数来读取其下一个字符,并当返回值返回,如果流中不存在更多的字符,就会返回常量值 EOF

EOF
stdio.h
头文件中定义的宏,即 #define EOF (-1)

如果想把单个字符写入到流,可以使用 fputc
函数,其原型如下所示。

int fputc(int ch, FILE *stream);

fputc
会将参数 ch
写入到参数 stream
指定的流中,写入成功后,会返回写入成功的字符;失败,则返回 EOF

字符串 I/O

除了单个字符的 I/O,C 语言还提供了字符串 I/O,用于输入和输出,其中的 fgets
函数用于读取字符串,其函数原型如下所示。

char *fgets(char *buffer, int bufsize, FILE *stream);

fgets
函数从参数指定的 stream
流中读取字符,并存储在 buffer
中,其返回值为该字符数组的首地址。当下次调用 fgets
时,会从流的下一个字符开始读取。当读取到文件尾时,fgets
函数会返回一个 NULL
指针。

读取字符时,会在最后将 \0
添加到末尾,构成一个字符串。

C 语言提供了 fputs
函数,用于将字符串写入到流中,其函数原型如下所示。

int fputs(char const *buffer, FILE *stream);

上面的函数在执行时,会将 buffer
逐字写入到 stream
中,并以 \0
结尾。当写入成功时,会返回非负值;失败会返回 EOF

二进制 I/O

二进制处理数据其实的效率最高的方法,避免了转换过程中的开销和损失,但并不利于人们阅读。在 C 语言中提供了 fread
函数和 fwrite
函数来读取和写入二进制数据,其原型如下所示。

size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);

  • buffer
    :保存数据的缓冲区。
  • size
    :缓冲区中每个字符的长度,即字节数。
  • count
    :读取或写入的字符数量。
  • stream
    :读取或写入的数据流。

count
的大小指定的是 buffer
中能存储的数量,而 size
的大小,指的是 buffer
中每个字符的大小,当读写一个标量时,count
的值应为 1
。两个函数的返回值是读写的元素个数,并非字节数目。当遇到文件尾或出错时,返回值会比 count
要小,可以使用  feof
ferror
检查。

缓冲区

每次打开流时,都会默认设置一个缓冲区,C 语言提供了 fflush
函数来强制使一个输出流的缓冲区内的数据进行物理写入,不管它是不是已经写满,其函数原型如下所示。

int fflush(FILE *stream);

调用该函数,可以将缓冲区中的数据立即写入。当传入的是 NULL
,则刷新所有打开的流的缓冲区。

当系统设置的缓冲不合适时,可以使用库函数提供的方式来修改缓冲区,其函数原型如下所示。

void setbuf(FILE *file, char *buf);

setbuf
函数缓冲区 buf
与文件指针 file
相关联,实现对流进行缓冲,而不用使用 fopen
函数打开时所默认的缓冲区。buf
的长度必须为 BUFSIZ
,它是 stdio.h
中所定义的宏。当 buf
传入的是 NULL
时,会关闭流的所有缓冲方式,无缓冲。

除了 setbuf
函数之外,还有一个函数也可以设置缓冲区,即 setvbuf
函数,其函数原型如下所示。

int setvbuf(FILE *file, char *buf, int mode, size_t size);

setvbuf
buf
为缓冲区地址,size
为指定缓冲区大小,并且使用 mode
参数指定缓冲方式,缓冲方式分为三种:全缓冲、行缓冲和无缓冲。

  • _IOFBF
    :全缓冲流,即当缓冲区填满时刷新,执行 I/O 操作。
  • _IOLBF
    :行缓冲流,即每当将换行符写入缓冲区时,缓冲区便进行刷新。
  • _IONBF
    :无缓冲的流,每次I//O操作都会被即时写入。

buf
size
参数配合来指定需要的缓冲区,如果 buf
传入的是 NULL
,那 size
就必须为 0

随机访问

以上介绍的函数只能以线性方式将数据写入,不能更改位置。因此,C 语言提供了随机访问 I/O,通过读取或写入来定需要的位置,实现任意顺序访问文件中的不同位置。如 ftell
函数,用来返回流的当前位置,其原型如下所示。

long ftell(FILE *stream);

ftell
是通过计算下一次读取或写入的位置距离文件起始位置的偏移量,来返回当前位置。在将来想要返回该位置时,就可以使用该函数返回的值。

通过 ftell
返回的值,可以使用 fseek
函数来在流中定位,并改变下一个读取或写入操作的位置,其函数原型如下所示。

int fseek(FILE *file, long offset, int origin);

fseek
中的 file
为指定需要改变的流,offset
参数为偏移量,是相对于 origin
参数所在位置的偏移量。而 origin
可以使用几个常量来表示需要锁定的位置,如下所示。

origin
含义
SEEK_SET
从流的起始位置开始, offset
偏移量为非负数
SEEK_CUR
从流的当前位置开始, offset
偏移量可正可负
SEEK_END
从流的尾部位置开始, offset
偏移量可正可负

该函数的返回值用于判断是否执行成功,如果返回值零值,证明执行成功;非零值为执行失败。

当然,还有一个函数,用于将读写指针重新设置回到指定流的起始位置,并同时清除流的错误提示标志,其原型如下所示。

void rewind(FILE *file);

除此之外,C 语言中的 fgetpos
fsetpos
函数可以作为 ftell
fseek
函数的替代。

判断状态

在流读取或写入成功时,程序就会一直运行下去来获得所要的结果,但有时候也会出现错误,C 语言提供了几个函数,用来判断当前流所处的状态。

当流处于文件尾或出现错误时,函数会返回 EOF
,使用 feof
函数就可以来判断。

int feof(FILE *file);

当传入的 file
处于文件尾或出错,该函数就会返回非零值,否则返回零值。但是光有 feof
函数来不能判断传入的流是处于文件尾还是错误了,这种情况就可以使用另一个函数,即 ferror
函数,来判断传入的流是否出错。

int ferror(FILE *file);

当该函数返回的值为非零值,表明读写出错;返回零值,表明未出错。这样就可以用于检测传入的流的状态了。

当调用 ferror
函数返回非零值,表示错误时,可以使用 clearerr
函数来对指定流的错误标志进行重置。

void clearerr(FILE *file);

调用 clearerr
后,在使用 ferror
检查同一个流时,返回值就变为 0
了。当出现错误标志时,可能会影响之后的操作,就可以使用 clearerr
函数来重置了。

临时文件

当想要使用一个临时文件来保存数据时,可以使用 tmpfile
函数,它创建的文件在程序结束时会被删除,其函数原型如下所示。

FILE *tmpfile(void);

该文件是以 wb+
模式打开,可用于二进制和文本数据。

如果想使用其他模式打开临时文件或者多程序使用,可以使用 tmpnam
函数创建一个临时文件名,其函数原型如下所示。

char *tmpnam(char *buffer);

创建的临时文件名,可使用 fopen
打开,并且在在使用时,必须使用 remove
去删除。

当函数传递的参数是指定长度至少为 L_tmpnam
的字符数组的指针,返回值为创建的文件名;当函数传递的参数是 NULL
,返回值为一个指向静态数据的指针,该数组包含了被创建的文件名。

tmpnam
创建的文件名是唯一的,并且调用次数不超过 TMP_MAX
,都会产生新名字。

操纵文件

除了对文件进行输入输出操作外,还可以操纵文件本身,如移动、删除等操作。

删除文件

remove
函数用于将指定文件做删除操作。

int remove(const char *filename);

remove
要删除的文件处于打开状态,是否删除取决于编译器。

修改文件名

rename
函数用于修改文件名称,其函数原型如下所示。

int rename(const char *oldname, const char *newname);

该函数的作用就是将原先的文件名 oldname
改为新的名字 newname
。当传入的文件名 newname
已经存在,是否要改名就要看编译器的了。如果失败,文件仍然可以使用原名来访问。




文章转载自海人为记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论