C语言文件操作:文本文件、二进制文件与文件定位
一、本篇文章要解决什么问题
你之前写的所有程序都有一个共同特点:数据存在变量里,程序一关,所有数据就没了。真实世界的程序需要持久化存储------把数据存到硬盘上,下次打开程序数据还在。
这篇文章帮你搞定三件事:
- C 语言怎么读写文件------fopen、fclose、fprintf、fscanf 的基本用法
- 文本文件和二进制文件有什么区别,分别用什么函数读写
- 怎么在文件里"跳来跳去"------fseek 和 ftell 的用法
二、先用一个简单例子理解
2.1 文件就是"笔记本"
- 打开文件(fopen):翻开笔记本到某一页,准备好笔
- 写入(fprintf):在纸上写字
- 读取(fscanf):读纸上的字
- 关闭(fclose) :合上笔记本放回书架------这一步非常重要,不合上可能导致内容丢失
2.2 文本文件 vs 二进制文件
- 文本文件:像 Word 文档------内容是人类可读的字符。用记事本打开能看到内容。
- 二进制文件:像 MP3 文件------内容是字节序列。用记事本打开看到的是乱码,但程序能精确解读。
三、核心知识点讲解
3.1 文件指针 FILE 和 fopen
C 语言通过 FILE * 类型的指针来操作文件:
c
FILE *fp = fopen("data.txt", "w"); // 以"写入"模式打开
if (fp == NULL)
{
printf("文件打开失败!\n");
return 1; // 必须检查!
}
fopen 的常用模式:
| 模式 | 含义 | 文件不存在时 | 文件存在时 |
|---|---|---|---|
"r" |
只读 | 返回 NULL | 从开头读 |
"w" |
只写 | 创建新文件 | 清空内容重写 |
"a" |
追加写入 | 创建新文件 | 在末尾追加 |
"r+" |
读写 | 返回 NULL | 从开头读写 |
"w+" |
读写 | 创建新文件 | 清空内容 |
"rb" / "wb" |
二进制读 / 写 | 同上 | 同上(二进制模式) |
每次 fopen 之后必须检查返回值是否为 NULL------文件可能因为权限、路径不存在等原因打开失败。

图18-1 程序与文件交互图:帮读者建立"程序操作文件"的完整概念模型。

图18-2 fopen 各模式行为表:供读者速查 fopen 模式的差异。
3.2 fprintf 和 fscanf------文本文件的读写
c
#include <stdio.h>
int main(void)
{
FILE *fp;
// 写入
fp = fopen("score.txt", "w");
if (fp == NULL)
{
printf("无法创建文件\n");
return 1;
}
fprintf(fp, "Tom 90\n");
fprintf(fp, "Jerry 85\n");
fclose(fp);
// 读取
fp = fopen("score.txt", "r");
if (fp == NULL)
{
printf("无法打开文件\n");
return 1;
}
char name[20];
int score;
while (fscanf(fp, "%19s %d", name, &score) == 2)
{
printf("%s: %d 分\n", name, score);
}
fclose(fp);
return 0;
}
运行结果:
text
Tom: 90 分
Jerry: 85 分
3.3 fgets 和 fputs------按行读写文本
c
// 逐行写入
fputs("第一行内容\n", fp);
fputs("第二行内容\n", fp);
// 逐行读取
char line[100];
while (fgets(line, sizeof(line), fp) != NULL)
{
printf("%s", line);
}
3.4 fread 和 fwrite------二进制读写
二进制读写按"数据块"来操作,适合结构化数据(如结构体):
c
#include <stdio.h>
typedef struct
{
int id;
double score;
} Record;
int main(void)
{
Record r1 = {1001, 95.5};
Record r2;
// 二进制写入
FILE *fp = fopen("record.bin", "wb");
if (fp == NULL) return 1;
if (fwrite(&r1, sizeof(Record), 1, fp) != 1)
{
printf("写入失败\n");
}
fclose(fp);
// 二进制读取
fp = fopen("record.bin", "rb");
if (fp == NULL) return 1;
if (fread(&r2, sizeof(Record), 1, fp) != 1)
{
printf("读取失败\n");
}
fclose(fp);
printf("读取:id=%d, score=%.1f\n", r2.id, r2.score);
return 0;
}

图18-3 文本文件与二进制文件的存储对比:解释两种文件模式的本质区别。
3.5 fseek 和 ftell------在文件中定位
c
fseek(fp, 0, SEEK_SET); // 跳到文件开头
fseek(fp, 0, SEEK_END); // 跳到文件末尾
fseek(fp, 5, SEEK_CUR); // 从当前位置往后跳 5 字节
long pos = ftell(fp); // 获取当前读写位置

图18-4 fseek 文件定位示意图:直观解释 fseek 的三个定位基准。
四、完整代码示例
一个"日记本"程序,支持追加写入和全文阅读:
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
FILE *fp;
char line[200];
int choice;
do
{
printf("\n===== 简易日记本 =====\n");
printf("1. 写一篇日记\n");
printf("2. 阅读所有日记\n");
printf("0. 退出\n");
printf("请选择:");
scanf("%d", &choice);
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) // 安全清缓冲区
{
;
}
switch (choice)
{
case 1:
fp = fopen("diary.txt", "a"); // 追加模式
if (fp == NULL)
{
printf("文件打开失败\n");
break;
}
printf("请输入内容(一行):");
fgets(line, sizeof(line), stdin);
fprintf(fp, "%s", line); // fgets 已包含 \n
fclose(fp);
printf("已保存!\n");
break;
case 2:
fp = fopen("diary.txt", "r");
if (fp == NULL)
{
printf("还没有日记,先写一篇吧\n");
break;
}
printf("\n===== 历史日记 =====\n");
while (fgets(line, sizeof(line), fp) != NULL)
{
printf("%s", line);
}
fclose(fp);
break;
case 0:
printf("再见!\n");
break;
default:
printf("无效选择\n");
}
} while (choice != 0);
return 0;
}
五、运行结果
text
===== 简易日记本 =====
1. 写一篇日记
2. 阅读所有日记
0. 退出
请选择:1
请输入内容(一行):今天学习了 C 语言文件操作
已保存!
===== 简易日记本 =====
1. 写一篇日记
2. 阅读所有日记
0. 退出
请选择:2
===== 历史日记 =====
今天学习了 C 语言文件操作
六、代码逐行解析
追加模式打开文件:
c
fp = fopen("diary.txt", "a");
"a"模式:文件存在时在末尾追加,不存在时创建- 不会覆盖原有内容------这是日记本的关键
scanf 后清缓冲区再接 fgets:
c
scanf("%d", &choice);
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) // 安全清掉残留 \n
{
;
}
fgets(line, sizeof(line), stdin); // 正常读取
这是第 8 篇重点讲过的缓冲区处理模式------在文件操作中同样重要。
fgets 读取到文件末尾自动返回 NULL:
c
while (fgets(line, sizeof(line), fp) != NULL)
这是最标准的"按行读取整个文件"的写法------fgets 读到文件末尾时返回 NULL,循环自动停止。
七、初学者常见错误
错误1:fopen 后不检查 NULL 就直接用
c
FILE *fp = fopen("noexist.txt", "r");
fprintf(fp, "data"); // fp 是 NULL,程序崩溃!
错误2:忘了 fclose
c
fp = fopen("data.txt", "w");
fprintf(fp, "hello");
// 忘了 fclose!数据可能还在缓冲区没写进硬盘
错误3:以 "w" 模式打开一个要保留内容的文件
c
// 本来想在原有内容后面追加,结果用了 "w"------旧内容全没了
fp = fopen("data.txt", "w"); // 应该用 "a"
错误4:fscanf 读取时的格式与文件中内容不匹配
c
// 文件内容:Tom 90
// 你写的:fscanf(fp, "%d %s", &score, name); 顺序错了!
// 结果 score 读不到数字,返回 EOF
错误5:二进制文件用文本模式打开(或反之)
c
// 写二进制数据但用了 "w" 而不是 "wb"
// 在 Windows 上,\n 可能被转换为 \r\n,破坏二进制数据
八、练习题
练习题1:写入并读取数字
让用户输入 5 个整数,程序把它们写入文件 numbers.txt(每行一个数字)。然后重新打开文件,把数字读出来,计算并输出它们的和。
练习题2:学生成绩文件存储
定义结构体 Student(学号、姓名、成绩),用户输入 3 个学生的数据,用 fprintf 写入 students.txt(每个学生一行,数据之间用空格分隔)。然后从文件中读取所有学生数据并显示。
练习题3:文件大小计算
用 fseek 和 ftell 写一个程序,打开任意文件(由用户输入文件名),计算并输出该文件的字节大小。
提示:fseek 跳到文件末尾,ftell 获取当前位置(即文件大小)。
九、本篇总结
- 文件操作用 FILE * 指针 ,fopen 打开后必须检查 NULL ,用完必须 fclose
- 文本文件 用 fprintf/fscanf 或 fgets/fputs,二进制文件用 fread/fwrite
- fopen 模式要选对 :
"r"只读,"w"清空重写,"a"追加 - fseek 和 ftell 可以在文件中任意定位,用于获取文件大小、跳过不需要的内容
- 文件操作后记得 fclose,否则数据可能丢失