12. 文件
12.1 文本文件、二进制文件与文件指针
文件操作的套路很固定:打开,检查,读写,关闭。哪一步失败都不能装作没看见。
文本文件保存的是字符,人能直接看懂,但数字读写时要做格式转换。二进制文件直接保存字节,通常更紧凑,不过会受类型大小、字节序和数据布局影响。
标准 I/O 用 FILE * 表示一个文件流。fopen 失败会返回 NULL,因此打开之后第一件事就是检查。
12.2 打开与关闭
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("data.txt");
return 1;
}
/* 读写文件 */
if (fclose(fp) == EOF) {
/* 关闭或刷新失败 */
}
打开模式主要看两件事:要读还是要写,原文件要保留还是清空。
| 模式 | 含义 |
|---|---|
r |
只读,文件必须存在 |
w |
只写;不存在则创建,存在则截断 |
a |
追加;写入总在文件末尾 |
r+ |
读写,文件必须存在 |
w+ |
读写,创建或截断 |
a+ |
读和追加 |
后面加 b 就是二进制模式,如 rb、wb。在 Windows 中它会影响换行转换;Unix 下通常没有区别。
w+ 和 w+b 都是“写、读”方式,区别在文本还是二进制;共同点是文件一打开,原内容就先被清空。需要保留旧数据时不能误用这两个模式。
12.3 文本读写
文本读写和键盘输入输出很像,只是多了一个文件指针:fprintf、fscanf 负责格式化数据,fputc、fgetc 每次处理一个字符,fputs、fgets 处理字符串或一整行。
int id;
double score;
while (fscanf(fp, "%d%lf", &id, &score) == 2) {
printf("%d %.1f\n", id, score);
}
上面循环里的 == 2 不能省,它表示这一轮确实读到了学号和成绩两个数据。循环结束后,再用 feof(fp) 判断是不是正常读到文件末尾,用 ferror(fp) 看有没有 I/O 错误;clearerr(fp) 可以清掉这两个状态。
格式化写文本时,数据之间要自己留空格、逗号或换行。连续写成 fprintf(fp, "%d%lf", a, x),两个数可能直接粘在一起,之后就不一定能按原来的格式读回来。
12.4 二进制读写
二进制文件不做字符串转换,直接按“每个元素多大、一共有几个元素”读写:
因此 fread、fwrite 的返回值也是成功处理的元素个数,不是字节数。比如要求读 10 个 int,返回 7 就表示只读到 7 个完整元素。
12.5 文件定位与缓冲
rewind(fp) 让文件位置回到开头。要从任意位置移动就用 fseek,起点可以选 SEEK_SET、SEEK_CUR 或 SEEK_END;ftell 则用来查看当前位置。这几个函数放在一起记,比单独背函数名好记。
文件刚写完时,位置通常已经在末尾。如果接下来还要从头读,先 rewind 或 fseek,否则第一次读取就可能失败。
fflush(fp) 做的是另一件事:把输出缓冲区真正写出去。课件里提到的 fflush(stdin) 在部分编译器上被当作清空输入,但这不是标准 C 的通用写法。