跳转至

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 就是二进制模式,如 rbwb。在 Windows 中它会影响换行转换;Unix 下通常没有区别。

w+w+b 都是“写、读”方式,区别在文本还是二进制;共同点是文件一打开,原内容就先被清空。需要保留旧数据时不能误用这两个模式。

12.3 文本读写

文本读写和键盘输入输出很像,只是多了一个文件指针:fprintffscanf 负责格式化数据,fputcfgetc 每次处理一个字符,fputsfgets 处理字符串或一整行。

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 二进制读写

二进制文件不做字符串转换,直接按“每个元素多大、一共有几个元素”读写:

size_t written = fwrite(a, sizeof a[0], n, fp);
size_t read_n = fread(a, sizeof a[0], n, fp);

因此 freadfwrite 的返回值也是成功处理的元素个数,不是字节数。比如要求读 10 个 int,返回 7 就表示只读到 7 个完整元素。

12.5 文件定位与缓冲

rewind(fp) 让文件位置回到开头。要从任意位置移动就用 fseek,起点可以选 SEEK_SETSEEK_CURSEEK_ENDftell 则用来查看当前位置。这几个函数放在一起记,比单独背函数名好记。

文件刚写完时,位置通常已经在末尾。如果接下来还要从头读,先 rewindfseek,否则第一次读取就可能失败。

fflush(fp) 做的是另一件事:把输出缓冲区真正写出去。课件里提到的 fflush(stdin) 在部分编译器上被当作清空输入,但这不是标准 C 的通用写法。

评论