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 | int fscanf(FILE* stream, const char* format, ...); |
下列代码就是等价于 scanf 和 printf 函数的:1
2
3int 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 | int fseek(FILE* stream, long int offset, int whence); |
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
2void 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
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;
}