C 语言中的文件流

常用 API 一览

函数作用注意事项
打开或关闭文件流fopen打开文件,返回指向 FILE 的指针需要指定模式(如“r”, “w”, “a”等),并处理可能的文件打开错误
fclose关闭文件流,释放流资源确保每个打开的文件流最终都被关闭,避免资源泄露
读写二进制文件流fread从文件流中读取数据需要检查返回值以确定读取的数据量和是否遇到错误或 EOF
fwrite向文件流中写入数据需要检查返回值以确保数据正确写入
逐字符读写文本文件流fgetc从文件流中读取下一个字符在达到文件末尾或出现错误时返回 EOF
fputc将一个字符写入文件流返回写入的字符,若出现错误则返回 EOF
逐行读写文本文件流fgets从文件流中读取字符串(一行)确保正确处理缓冲区长度和行尾字符
fputs将字符串(不包括 NUL 终止符)写入文件流不会自动添加换行符,需手动处理
格式化读写文本文件流fscanf从文件流中格式化读取数据scanf 类似,但从文件流读取
fprintf格式化输出到文件流类似于printf,但输出到文件流
文件流状态检查feof检查文件流的 EOF 标志通常用于循环读取数据时判断文件是否结束
ferror检查文件流的错误指示器用于错误处理和调试
文件定位ftell返回文件流的当前文件位置指示器配合 fseek 可实现文件的随机访问
fseek设置文件流的文件位置指示器可用于移动文件指针到特定位置
rewind将文件流的文件位置指示器重置到文件开头等同于fseek(stream, 0, SEEK_SET)
刷新输出缓冲区fflush清空文件流的输出缓冲区常用于确保所有输出已经从缓冲区写入文件

标准流

文件指针刷新机制默认含义
stdin标准输入流行缓冲从键盘读取
stdout标准输出流行缓冲输出到屏幕(终端)
stderr标准错误流不缓冲输出到屏幕(终端)

标准流也可以作为文件流参数使用。

打开文件流

fopen 函数

1
FILE* fopen(const char* filename, const char* mode);
  • 两个参数都是字符串
  • 第一个参数是文件的路径,可以是相对路径也可以是绝对路径。
  • 第二个参数是打开此文件流的模式。
  • 记得判空

FILE* 指针(流)

FILE 结构体对象是 C 语言标准库提供的一个抽象,用于表示一个打开的文件及其相关的状态信息,包括缓冲区的状态、 文件位置指示器、错误指示器等。

FILE 包含了文件操作中一切 C 程序员需求的信息,我们通过 FILE* 指针操作这个结构体。

可以把”FILE*”指针,简化看成一个指示文件当前读写位置的文件指针。刚开始 open 文件时:

  • 非追加写入模式下,那么文件指针就会从文件开头开始移动。
  • 追加写入模式下,读操作的指针仍然是从头开始,但写操作时指针会从文件末尾开始移动。

打开文本文件的模式

模式字符串模式名称含义
“r”只读模式打开一个 已存在的 文件,进行读操作,此模式不会创建文件也不会截断原文件。从文件头开始读
“w”只写模式打开文件,若文件不存在,会创建文件。若文件存在,则会截断清空原文件所有数据。文件指针从文件头开始写
“a”追加写入模式打开文件用于写操作,文件不存在则创建,存在则在文件末尾追加写入
“r+”读写模式打开一个 已存在的文件,进行读写操作,此模式不会创建文件也不会截断原文件。从文件头开始读写
“w+”读写模式打开文件,若文件不存在,会创建文件。若文件存在,则会截断清空原文件所有数据。文件指针从文件头开始读写
“a+”追加读写模式打开文件用于读写操作,文件不存在则创建,存在则在文件末尾追加写入

关闭文件流

1
int fclose(FILE* stream);
  • 如果成功关闭流,此函数返回 0,否则返回 EOF。

读和写文件数据

fgetc 和 fputc 读写一个字符

1
int fgetc(FILE* stream);
  • 读取成功,返回读取的字符
  • 读到文件末尾,或者读取失败,返回 EOF

fgetc 和 getchar 类似。不同的是 getchar 只能从标准输入流 (stdin) 中读取字符,而 fgetc 可以从任意一个输入流中读取字符。

1
int fputc(int c, FILE* stream);
  • 写入成功,返回读取的字符
  • 写入失败,返回 EOF

fputc 和 putchar 类似。不同的是 putchar 只能向标准输出流 (stdout) 中写入字符,而 fputc 可以向任意一个输出流中写入字符。

fgets 和 fputs 读写一整行字符

1
char* fgets(char* str, int count, FILE* stream);
  • 参数 str: 指向一个字符数组
  • 参数 count:表示此函数最多会从输入流中读取 count - 1 个字符,然后存入 str 数组。(也就是需要传入 str 指向字符数组的长度)
  • 参数 stream: 任意输入流
  • 成功读到一行字符,返回 str 指针。
  • 读取失败或读到了末尾,返回空指针 NULL

fgets 遇到换行符’\n’,或者文件的末尾就会终止 (也就是说,读取的字符数可能不足 count - 1 个) 字符的读取操作,该函数会在字符数据的最后加上空字符,以表示一个字符串。并且在空间充足的情况下,还会存储换行符 \n。

fgets 也比 gets 更为安全,因为它限制了读取字符的最大数目(count - 1)。此外,如果 fgets 是因为读取了换行符而终止,在空间充足时,它还会存储换行符’\n’,而 gets 函数从来不会存储换行符。

1
int fputs(const char* str, FILE* stream);
  • 参数 str: 要写到输出流的字符串(必须是以’\0’结尾的字符串)
  • 参数 stream: 任意输出流
  • 写入成功,那么会返回一个非负值(true)
  • 写入失败会返回 EOF

fputs 是 puts 的通用版本,它可以将字符串写入到任意的输出流中,而 puts 只能写入到 stdout 中。此外,fputs 是原样输出字符串,而 puts 会在字符串后面而外输出一个换行符’\n’。

fscanf 和 fprintf 格式化读写

1
2
int fscanf(FILE* stream, const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);

下列代码就是等价于 scanf 和 printf 函数的:

1
2
3
int num;
fscanf(stdin, "%d", &num);
fprintf(stdout, "%d\n", num);

fread 和 fwrite 读写二进制数据

1
size_t fread(void* buffer, size_t size, size_t count, FILE* stream); 
  • 参数
    • buffer: 存放从输入流中读取到的数据的数组(普遍会用一个 - char 数组,因为 char 数组一个元素一个字节)
    • size: 每个元素的大小(以字节为单位)
    • count: 最多一次性读取的元素的个数(一般都设置为数组长度)
    • stream: 任意输入流
  • 返回值
    • 成功读取元素的个数。当读到文件末尾时,返回值可能会小 - 于 count(因为已经读不到 count 个字节数据了)。
    • 如果读到了流的末尾,再继续 read,此函数会返回 0。
    • 除此外,若在读取过程中发生了错误,此函数也会返回一个小于 count 的值。
1
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
  • buffer: 存放待输出字节数据的数组(普遍会用一个 char 数组,因为 char 数组一个元素一个字节)
  • size: 每个元素的大小(以字节为单位)
  • count: 要写入元素的个数
  • stream: 任意输出流
  • 返回值:成功写入元素的个数。当发生错误时,这个值可能小于 count。(该返回值一般不处理)

文件定位

1
2
3
int fseek(FILE* stream, long int offset, int whence);
long int ftell(FILE* stream);
void rewind(FILE* stream);

fseek 可以改变文件指针的位置,其中核心的参数是 whence 表示参照点,参照点有 3 个选择:

  • SEEK_SET:文件的起始位置。
  • SEEK_CUR:文件的当前位置。
  • SEEK_END:文件的末尾位置。

offset 表示偏移量 (可以为负),它是以字节进行计数的。表示从参照点开始,向文件开头 (负数) 或者向文件末尾 (正数) 移动 offset 个字节。

ftell函数,即”file tell”,它表示记录文件指针的当前位置,以待后续返回。这一点看,它有点类似 goto 中的标签。

rewind,倒带的英文单词。表示直接将文件指针移动到文件开始。

errno 变量

errno 是一个 int 类型的全局变量:

  • 在 C11 标准前的 C 语言规范中,它就是一个进程共享的全局变量
  • C11 标准引入线程的概念,该变量成为线程私有的全局变量。即每一个线程都有自己独立的 errno 变量
  • 此全局变量被定义在 <errno.h> 头文件中

我们可以在程序的任何位置打印 errno 的值,如果显示的是非 0 值,就表示发生了某种类型的错误。如果想要详细的查看错误信息,而不是一个错误码,可以用以下两个函数:

1
2
void perror(const char *s);
char *strerror(int errnum);

perror 函数:

  • 全称是”Print Error”,p 是 print 的缩写
  • 该函数会根据当前 errno 的值,向标准错误流 stderr 输出一个描述对应错误的信息。
  • 该函数允许传参一个字符串,这个字符串会拼到错误信息的前面,最终一起输出到 stderr 中。
  • 因为可以传参,使得该函数可以在指示错误时,传递一些额外信息,在实际工作中进行错误输出时,是比较常用的。比如对于因找不到文件而打开文件失败的错误,我们就可以通过该函数添加上找不到的文件的名字。

strerror 函数:

  • 全称是”String Error”,str 是 string 的缩写
  • 该返回会根据函数调用传入的 errno 的值,找到对应的错误信息,然后包装成字符串作为返回值返回。
  • 该返回的返回值是一个包含错误信息的字符串。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <errno.h>
#include <string.h> // 包含以调用 strerror 函数

int main(void) {
printf("%d\n", errno); // 进程启动时设置为 0
FILE* fp = fopen("not_exist_file.txt", "r");
// 上面的文件打开失败
printf("%d\n", errno); // 此时 errno 被设置为 2
puts(strerror(errno)); // 根据 errno 返回一个错误信息字符串

// 一般更常用 perror, 它表示 print error
// 它可以传递一个参数, 并将参数拼到错误信息字符串中
// 然后输出到 stderr 当中
perror("not_exist_file.txt");
return 0;
}