程序跑完数据就没了,这是很多人学 C 语言时的第一道坎。文件操作就是解决这件事的:把数据存进磁盘,程序结束还在,下次运行还能读回来。下面把文件读写的整套流程走一遍。
为什么要用文件
- 数据持久化:程序终止后数据不丢,下次运行接着用。
- 批量数据:上千条记录用手敲要命,文件里存好,几行代码读完。
- 跨机器迁移:把文件拷走,数据跟着走,不用改程序。
两类文件
- 文本文件
- 普通的 .txt,记事本就能编辑,人眼直接看得懂,好维护但占空间大、没什么安全可言。
- 二进制文件
- .bin 之类,数据按内存里的二进制形态存,人眼读不了,但存得多、也更紧凑。
C 语言里文件读写全靠 stdio.h,核心就是那句文件指针:
FILE *fptr; /* 程序和文件之间的通道 */打开文件:fopen()
fopen() 负责打开(或创建)文件,第一个参数是文件名/路径,第二个是模式:
FILE * fopen(const char *file_name, const char *access_mode);常用的模式:
| 模式 | 含义 | 文件不存在时 |
|---|---|---|
r | 只读 | 返回 NULL |
w | 只写,内容清空重写 | 新建文件 |
a | 追加,写到末尾 | 新建文件 |
r+ | 读写 | 返回 NULL |
w+ | 读写,内容清空重写 | 新建文件 |
a+ | 读写追加 | 新建文件 |
rb / wb / ab… | 上述模式 + 二进制方式 | 同文本规则 |
打开文件必须检查返回值,NULL 说明没打开成(文件不存在、权限不够等):
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fptr;
fptr = fopen("filename.txt", "r");
if (fptr == NULL) {
printf("The file is not opened.\n");
exit(1);
}
return 0;
}输出:
The file is not opened.因为 filename.txt 在源码目录里根本不存在。换成 "w" 模式,文件不存在也会被新建出来:
FILE *fptr = fopen("file.txt", "w");
if (fptr == NULL)
printf("The file is not opened.\n");
else
printf("The file is created successfully.\n");注意 Windows 下路径里的反斜杠要写成 \\,比如 "E:\\cprogram\\new.txt"。
写文本文件:fprintf() 和 fputs()
fprintf() 就是 printf() 的文件版,多传一个文件指针;fputs() 往文件写一行字符串。
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fptr;
char data[50] = "This is a line of text.";
fptr = fopen("file.txt", "w");
if (fptr == NULL) {
printf("The file is not opened.\n");
exit(1);
}
fputs(data, fptr); /* 写一行 */
fputs("\n", fptr);
fprintf(fptr, "The number is %d\n", 42); /* 格式化写 */
fclose(fptr);
return 0;
}程序从键盘输入一个数存进文件也行:
int num;
FILE *fptr = fopen("program.txt", "w");
if (fptr == NULL) { printf("Error!\n"); exit(1); }
printf("Enter num: ");
scanf("%d", &num);
fprintf(fptr, "%d", num); /* 写进文件 */
fclose(fptr);读文本文件:fscanf() 和 fgets()
fscanf() 是 scanf() 的文件版,fgets() 一次读一整行,配合循环能读到文件尾(EOF)。
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fptr;
char data[50];
fptr = fopen("file.txt", "r");
if (fptr == NULL) {
printf("file.txt failed to open.\n");
exit(1);
}
while (fgets(data, 50, fptr) != NULL) {
printf("%s", data); /* 一行行打出来 */
}
fclose(fptr);
return 0;
}读文件时有两个细节:
- 读完一个字符/一段数据后,文件指针会自动移到刚才读到的位置。
- 读到文件结尾,函数返回 EOF,所以循环用
!= NULL或!= EOF判断,别傻乎乎一直读。
读一个整数也很常见(配合上面写入的例子):
int num;
FILE *fptr = fopen("program.txt", "r");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
fscanf(fptr, "%d", &num); /* 从文件里读整数 */
printf("Value of n = %d\n", num);
fclose(fptr);关闭文件:fclose()
文件用完必须关,把文件从内存里清掉、释放句柄:
fclose(fptr);忘了关文件,数据可能没落盘、句柄泄漏,跑得久的程序迟早出事。
移动位置:fseek()、rewind()、ftell()
文件指针不是只能顺着读。fseek() 能把它挪到任意位置,第三个参数指定从哪算:
fseek(fptr, offset, whence);| 常量 | 含义 |
|---|---|
SEEK_SET | 从文件开头算 |
SEEK_END | 从文件结尾算 |
SEEK_CUR | 从当前位置算 |
offset 可为正可负。比如"替换文末最后 6 个字符前的内容":
fputs(data, fptr);
fputs("\n", fptr);
fseek(fptr, -6, SEEK_END); /* 回到结尾往前 6 字节 */
fputs("new text", fptr); /* 从这里覆盖写 */
fclose(fptr);另外两个常用助手:
rewind(fptr):把指针挪回文件开头。ftell(fptr):返回当前指针位置(偏移量)。
读写二进制文件:fwrite() 和 fread()
二进制文件用 rb、wb 这些模式打开,写入用 fwrite(),读出用 fread()。两者的参数一模一样:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);- ptr:要写/读的内存地址
- size:每个元素多大(字节)
- nmemb:写/读几个元素
- stream:文件指针
把整个结构体直接存进去,是二进制文件最爽的用法,省去逐字段格式化:
#include <stdio.h>
#include <stdlib.h>
struct threeNum {
int n1, n2, n3;
};
int main() {
int n;
struct threeNum num;
FILE *fptr;
/* 写入 */
fptr = fopen("binaryFile.bin", "wb");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
for (n = 1; n < 5; ++n) {
num.n1 = n;
num.n2 = 5 * n;
num.n3 = 5 * n + 1;
fwrite(&num, sizeof(struct threeNum), 1, fptr);
}
fclose(fptr);
/* 读出 */
fptr = fopen("binaryFile.bin", "rb");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
for (n = 1; n < 5; ++n) {
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
}
fclose(fptr);
return 0;
}输出:
n1: 1 n2: 5 n3: 6
n1: 2 n2: 10 n3: 11
n1: 3 n2: 15 n3: 16
n1: 4 n2: 20 n3: 21读二进制配合 fseek() 还能实现倒着读:先挪到结尾往前一个结构体大小,读完再往回跳:
fseek(fptr, -sizeof(struct threeNum), SEEK_END);
for (n = 1; n < 5; ++n) {
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
fseek(fptr, -2 * sizeof(struct threeNum), SEEK_CUR);
}文件操作函数一览
| 函数 | 作用 |
|---|---|
fopen() | 打开/创建文件 |
fclose() | 关闭文件 |
fprintf() / fscanf() | 格式化读写文本 |
fputs() / fgets() | 写/读整行 |
fputc() / fgetc() | 写/读单个字符 |
fwrite() / fread() | 二进制读写 |
fseek() / rewind() / ftell() | 移动/重置/获取指针位置 |
文件操作的套路就四步:打开 → 检查 NULL → 读写 → 关闭。走顺了,日志、配置、存档、导入导出都能顺手搞定。