第23讲:文件操作
📚 第23讲:文件操作 📁💾
从“内存瞬时记忆”到“硬盘永久存储”,掌握C语言的“数据存档术”!
让程序学会“记住昨天的故事”!
📂 目录
- 为什么使用文件? —— 程序的“永久记忆” 🧠
- 什么是文件? —— 程序与数据的“仓库” 🏭
- 二进制文件 vs 文本文件 —— 两种存储“密码” 🔐
- 文件的打开和关闭 —— 建立“连接”的仪式 🔗
- 文件的顺序读写 —— 逐字逐句的“读书写字” 📖✍️
- 文件的随机读写 —— “任意跳转”的快进键 ⏭️
- 文件读取结束的判定 —— 别再误用
feof
! ❌ - 文件缓冲区 —— 内存与磁盘的“中转站” 🚚
📖 正文开始
你有没有遇到过这样的问题:
- 程序运行完,数据就没了?
- 想保存用户上次的设置,却无能为力?
这时候,文件操作就派上用场了!
它让程序的数据不再“朝生暮死”,而是可以持久化存储,随用随取!
为什么使用文件? —— 程序的“永久记忆” 🧠
🌟 生活比喻:笔记本 vs 记忆力
- 内存:像你的“短期记忆”——程序一关,数据就忘。
- 文件:像“笔记本”——写下来,永远记得。
✅ 文件的核心价值:数据持久化!
即使程序关闭、电脑重启,数据依然存在!
什么是文件? —— 程序与数据的“仓库” 🏭
📦 两大类文件
类型 | 说明 | 示例 |
---|---|---|
程序文件 | 构成程序的代码文件 | .c , .obj , .exe |
数据文件 | 程序读写的数据 | 用户配置、日志、数据库 |
✅ 本章重点:数据文件——让程序学会“读写外部数据”!
📂 文件名:文件的“身份证”
一个文件必须有唯一标识,格式为:
路径 + 文件名主干 + 后缀
例如:C:\code\test.txt
✅ 简称“文件名”,是程序访问文件的“钥匙”!
二进制文件 vs 文本文件 —— 两种存储“密码” 🔐
🔍 核心区别:存储方式不同
类型 | 存储方式 | 特点 | 示例 |
---|---|---|---|
文本文件 | 以ASCII码存储 | 人类可读,占用空间大 | "10000" → 5字节 |
二进制文件 | 原始二进制形式 | 机器高效,人类难读 | 10000 → 4字节 |
✅ 实验:10000的存储差异
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf); // 二进制写入
fclose(pf);
🔍 在VS中打开
test.txt
:
你会看到一堆“乱码”——这正是10000
的二进制表示!
它只占 4字节,而文本形式要占 5字节!
文件的打开和关闭 —— 建立“连接”的仪式 🔗
🔌 核心函数:fopen
和 fclose
FILE* fopen(const char* filename, const char* mode);
int fclose(FILE* stream);
✅ 黄金法则:打开必关闭,否则“内存泄漏”!
🌊 什么是“流”(Stream)?
想象一条“数据之河”:
- 程序是源头
- 文件/屏幕/键盘是河流的终点
流
就是这条河,数据在其中流淌
🧭 标准流:默认打开的“三条河”
流 | 说明 | 默认设备 | 常用函数 |
---|---|---|---|
stdin | 标准输入流 | 键盘 | scanf , getchar |
stdout | 标准输出流 | 屏幕 | printf , putchar |
stderr | 标准错误流 | 屏幕 | perror |
✅ 为什么
printf
不用打开?因为stdout
已默认打开!
📌 文件指针:文件的“遥控器”
FILE* pf; // 文件指针
- 每个打开的文件,系统都会创建一个
FILE
结构体,存储文件信息(名字、位置等)。 pf
指向这个结构体,通过它就能“遥控”文件。
✅ 你可以不知道
FILE
内部细节,但必须会用FILE*
!
🔐 文件打开模式(mode)速查表
模式 | 含义 | 不存在时 |
---|---|---|
"r" | 读文本 | 报错 |
"w" | 写文本 | 创建 |
"a" | 追加文本 | 创建 |
"rb" | 读二进制 | 报错 |
"wb" | 写二进制 | 创建 |
"ab" | 追加二进制 | 创建 |
"r+" | 读写文本 | 报错 |
"w+" | 读写文本 | 创建 |
"a+" | 读写追加 | 创建 |
💡 记忆口诀:
r
开头:文件必须存在w
开头:文件不存在就创建,存在就清空!a
开头:追加,永不覆盖!
✅ 示例:写入文件
FILE* pFile = fopen("myfile.txt", "w");
if (pFile != NULL) {fputs("Hello, File!", pFile);fclose(pFile); // ✅ 必须关闭!
}
文件的顺序读写 —— 逐字逐句的“读书写字” 📖✍️
📚 顺序读写函数一览
函数 | 功能 | 适用流 |
---|---|---|
fgetc / fputc | 字符读写 | 所有流 |
fgets / fputs | 字符串读写 | 所有流 |
fscanf / fprintf | 格式化读写 | 所有流 |
fread / fwrite | 二进制读写 | 文件流 |
🔁 对比三剑客
函数 | 说明 |
---|---|
scanf / printf | 默认操作 stdin / stdout |
fscanf / fprintf | 可指定文件流 |
sscanf / sprintf | 在字符串中读写(内存操作) |
✅ 示例:格式化写入
fprintf(pFile, "Name: %s, Age: %d\n", "Alice", 25);
文件的随机读写 —— “任意跳转”的快进键 ⏭️
🎯 fseek
:定位“文件光标”
int fseek(FILE* stream, long offset, int origin);
origin
起点:SEEK_SET
(文件头)、SEEK_CUR
(当前位置)、SEEK_END
(文件尾)offset
偏移量:正数向后,负数向前
✅ 示例:修改文件内容
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET); // 光标跳到第9个字符
fputs(" sam", pFile); // 写入 → "This is a sample."
📏 ftell
:获取当前“位置”
long ftell(FILE* stream);
✅ 示例:获取文件大小
fseek(pFile, 0, SEEK_END); // 跳到末尾
long size = ftell(pFile); // 获取位置 → 文件大小
🔄 rewind
:一键回到“起点”
void rewind(FILE* stream);
✅ 示例:读取刚写入的内容
for (char c = 'A'; c <= 'Z'; c++) {fputc(c, pFile);
}
rewind(pFile); // 回到开头
fread(buffer, 1, 26, pFile); // 读取全部
文件读取结束的判定 —— 别再误用 feof
! ❌
⚠️ 重要警告:feof
不是“结束判断器”!
feof
的真正作用:
在读取失败后,判断是否因为“遇到文件尾”导致的失败。
✅ 正确的结束判断方法
文件类型 | 正确做法 |
---|---|
文本文件 | 检查 fgetc 是否为 EOF ,或 fgets 是否为 NULL |
二进制文件 | 检查 fread 返回值是否小于期望读取的数量 |
✅ 示例1:文本文件读取
int c;
while ((c = fgetc(fp)) != EOF) { // ✅ 正确:用返回值判断putchar(c);
}// 读取结束后,再用 feof 判断原因
if (feof(fp)) {puts("成功到达文件末尾");
} else if (ferror(fp)) {puts("读取时发生错误");
}
✅ 示例2:二进制文件读取
size_t ret = fread(b, sizeof(double), SIZE, fp);
if (ret == SIZE) {puts("读取成功!");
} else {if (feof(fp)) puts("意外到达文件末尾");if (ferror(fp)) perror("读取错误");
}
✅ 记住口诀:
“先读再判,feof断因,不作判据!”
文件缓冲区 —— 内存与磁盘的“中转站” 🚚
🌟 生活比喻:快递中转仓
- 你下单(写数据)→ 快递员不马上送(不马上写磁盘)→ 先存中转仓(内存缓冲区)
- 仓库存满 → 统一发货(刷新到磁盘)
🔍 缓冲区的工作原理
- 输出:数据先到内存缓冲区,满后才写入磁盘。
- 输入:磁盘数据先读入缓冲区,程序再从缓冲区取。
⚠️ 危险实验:不刷新的后果
FILE* pf = fopen("test.txt", "w");
fputs("Hello", pf);
Sleep(10000); // 睡10秒 → 打开test.txt,发现是空的!
fflush(pf); // 刷新缓冲区 → 文件立刻有内容
fclose(pf); // fclose 也会自动刷新!
✅ 关键结论:
- 使用
fflush(pf)
可手动刷新缓冲区。fclose(pf)
会自动刷新并关闭。- 不刷新,数据可能“滞留”内存,未写入磁盘!
🎯 总结:文件操作“三要三不要”
要 ✅ | 不要 ❌ |
---|---|
要 fopen 后检查 NULL | 不要忘记 fclose |
要正确判断文件结束 | 不要直接用 feof 判断结束 |
要理解缓冲区的存在 | 不要假设 fputs 立刻写入磁盘 |
🎯 恭喜你!
你已经掌握了C语言的“数据存档术”!
现在,你的程序不仅能“思考”,还能“记忆”了!
下一站:预处理指令,揭开C语言“编译前的秘密”!🔍🚀