程序跑完就消失的数据没有意义,把结果存进文件、从文件读入配置才是常态。C 用 FILE * 完成文件读写,流程永远是四步:打开 → 操作 → 检查 → 关闭。
1. 文件指针与 fopen
所有文件操作都围绕 FILE * 进行。fopen(路径, 模式) 打开文件,模式决定"读还是写":
| 模式 | 含义 | 文件不存在时 |
|---|---|---|
| "r" | 只读 | 失败返回 NULL |
| "w" | 只写,清空原内容 | 自动创建 |
| "a" | 追加写 | 自动创建 |
| "r+" / "w+" | 读写 | "w+" 会清空 |
| "rb" / "wb" | 二进制读写 | — |
fopen 失败返回 NULL,必须检查,否则解引用 NULL 会崩溃。
2. 写入文件
写文本用 fprintf(格式化)或 fputs(整行):
#include <stdio.h>
int main(void) {
FILE *fp = fopen("notes.txt", "w");
if (fp == NULL) {
printf("无法打开文件\n");
return 1;
}
fprintf(fp, "Hello, C File!\n");
fprintf(fp, "第 %d 行: 今天写了 %d 行代码\n", 2, 120);
fputs("fputs 写的最后一行\n", fp);
fclose(fp);
printf("写入完成\n");
return 0;
}
写完务必 fclose:缓冲区里的内容要等关闭(或 flush)才会真正落盘。
3. 读取文件
逐行读取用 fgets,它自带边界保护,不会像 gets 那样溢出;格式化读取用 fscanf:
#include <stdio.h>
int main(void) {
FILE *fp = fopen("notes.txt", "r");
if (fp == NULL) {
printf("文件不存在\n");
return 1;
}
char line[100];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("读到: %s", line); // fgets 会保留换行符
}
fclose(fp);
return 0;
}
读结构化数据时 fscanf 更方便,但它对格式错误敏感,生产代码建议配合 fgets + sscanf 使用。
4. 二进制文件
结构体等内存数据直接落盘用 fwrite/fread,按"块"读写:
#include <stdio.h>
struct Record {
int id;
double value;
};
int main(void) {
struct Record recs[2] = {{1, 3.14}, {2, 2.71}};
FILE *fp = fopen("data.bin", "wb");
fwrite(recs, sizeof(struct Record), 2, fp);
fclose(fp);
struct Record readback[2];
fp = fopen("data.bin", "rb");
fread(readback, sizeof(struct Record), 2, fp);
fclose(fp);
printf("读回: id=%d value=%.2f\n", readback[1].id, readback[1].value);
return 0;
}
二进制文件读写快、无格式转换开销,但跨平台、跨编译器时要注意结构体对齐和字节序问题。
5. 错误处理与文件结尾
文件操作必须处理两类情况:打开失败和读到结尾。用 feof 判断是否到文件尾:
#include <stdio.h>
int main(void) {
FILE *fp = fopen("scores.txt", "r");
if (fp == NULL) {
perror("打开失败"); // 打印具体错误原因
return 1;
}
int score, count = 0, sum = 0;
while (fscanf(fp, "%d", &score) == 1) { // 读到才继续
sum += score;
count++;
}
if (feof(fp)) {
printf("平均 %.1f\n", sum / (double)count);
}
fclose(fp);
return 0;
}
判断读取循环的终止条件,用 fscanf 的返回值比 feof 更可靠——feof 只在尝试读过结尾之后才为真。
把读写能力串起来,用 1KB 缓冲区批量搬运文件——缓冲区大小正是文件 IO 性能的关键:
💡 学习建议:做一个"记账本"小程序:支持添加记录、保存到文件、下次启动时读入。把文件读写、结构体、循环全用上,比背十个 API 都有效。