01/14
📝 编辑模式 · 点击文字编辑 · Ctrl+S 保存导出 · 按 E 退出
{ —— 谭浩强《C程序设计》第五版 —— }

第10章
文件输入输出

对文件的输入输出 · File I/O in C

📖 P.299-P.338 🏷️ fopen / fclose / fread / fwrite / fseek 📅 课时:4学时

按 空格键 / ↓ 翻页  |  按 E 进入编辑模式

10.0 · OVERVIEW

📋 章节知识地图
┌─────────────────────────────────────────────────────┐ │ 第10章 · 文件输入输出 │ ├─────────────────────────────────────────────────────┤ │ │ │ 10.1 文件概述 ──→ ASCII文件 vs 二进制文件 │ │ 10.2 文件类型指针 ──→ FILE *fp │ │ 10.3 打开与关闭 ──→ fopen() / fclose() │ │ 10.4 顺序读写 ──→ fputc/fgetc fgets/fputs │ │ → fprintf/fscanf fread/fwrite │ │ 10.5 随机读写 ──→ fseek() ftell() rewind() │ │ 10.6 出错检测 ──→ ferror() feof() clearerr() │ │ │ │ 🎯 重点:文件指针 · 打开模式 · 顺序vs随机读写 │ │ ⚠️ 难点:二进制读写 · fseek定位 · 缓冲区理解 │ └─────────────────────────────────────────────────────┘
🔑 核心概念
文件是存储在外部介质上数据的集合。
C把文件看作一个字节序列,分为ASCII文件和二进制文件。
所有文件操作都通过 FILE 结构体指针进行。
📊 学习路线
① 理解文件概念 → ② 掌握fopen/fclose → ③ 学会顺序读写 → ④ 随机定位fseek → ⑤ 错误处理 → ⑥ 综合实战

10.1 · C-FILE

📄 文件概述
📝 ASCII文件(文本文件)
每个字节存放一个ASCII码,代表一个字符。
例:整数 12345
占5个字节:31 32 33 34 35
可直接用文本编辑器打开阅读。
💾 二进制文件
把内存中的数据按原样输出到磁盘。
例:int 12345 占2或4字节(取决于系统)
节省空间,但不能直接阅读。
/* === ASCII vs 二进制存储对比 === */ int num = 12345; // 内存中4字节 // ASCII格式写入: // '1''2''3''4''5' → 5 字节 fprintf(fp, "%d", num); // 二进制格式写入: // 0x00003039 → 4 字节 fwrite(&num, sizeof(int), 1, fp); // ★ 二进制文件更紧凑, // 但不可直接阅读

10.2 · FILE POINTER

🔗 文件类型指针 FILE
/* FILE 结构体(stdio.h 中定义) */ typedef struct { short _level; // 缓冲区满/空 unsigned _flags; // 文件状态标志 char _fd; // 文件描述符 unsigned char _hold; // ungc字符 short _bsize; // 缓冲区大小 unsigned char *_base; // 缓冲区位置 unsigned char *_curp; // 当前指针 // ... 更多字段 } FILE;
📌 核心理解
FILE *fp; — 定义文件指针变量

fp 不是指向文件本身,而是指向内存中的一个 FILE 结构体。
这个结构体包含了文件的全部信息:
· 缓冲区位置和大小
· 当前读写指针位置
· 文件打开模式
· 错误状态标记
💡 类比理解
FILE *fp 就像一本书的"借书卡":
你不直接拿着书(磁盘文件),而是通过借书卡(FILE结构体)来记录和管理对书的操作状态。

10.3.1 · FOPEN

🔓 fopen 打开文件
/* 函数原型 */ FILE *fopen(const char *filename, const char *mode); /* 常见调用 */ FILE *fp; fp = fopen("data.txt", "r"); // 只读 fp = fopen("output.dat", "wb"); // 只写二进制 fp = fopen("log.txt", "a"); // 追加 if (fp == NULL) { printf("文件打开失败!\n"); exit(1); }

📋 文件打开模式速查表

模式含义文件不存在文件存在指针位置
"r"只读出错正常打开文件开头
"w"只写新建覆盖文件开头
"a"追加新建保留文件末尾
"r+"读写出错正常打开文件开头
"w+"读写新建覆盖文件开头
"a+"读写新建保留文件末尾
"rb"/"wb"/"ab"加 b = 二进制模式,功能同上

10.3.2 · FCLOSE

🔒 fclose 关闭文件
⚠️ 为什么必须关闭?
① 将缓冲区数据真正写入磁盘
② 释放 FILE 结构体内存
③ 断开程序与文件的连接
④ 防止数据丢失和资源泄漏

fclose(fp); — 返回0成功,EOF(-1)失败
/* === 文件操作完整生命周期 === */ ① 定义文件指针 FILE *fp; ② 打开文件 fp = fopen("data.txt","r"); if(fp==NULL) { 错误处理 } ③ 读写操作 fscanf(fp,"%d",&n); fprintf(fp,"%s",str); ④ 关闭文件 fclose(fp);
/* 标准文件操作模板 */ #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; if ((fp = fopen("file.txt", "r")) == NULL) { printf("无法打开文件!\n"); exit(1); } // ... 文件操作 ... fclose(fp); return 0; }

10.4.1-10.4.3 · SEQUENTIAL R/W

📝 字符 & 字符串输入输出
函数调用格式功能返回值
fgetcch = fgetc(fp)从fp读一个字符成功返回字符,失败返回EOF
fputcfputc(ch, fp)写一个字符到fp成功返回ch,失败返回EOF
fgetsfgets(str, n, fp)从fp读n-1个字符到str成功返回str,失败返回NULL
fputsfputs(str, fp)把str写到fp成功返回≥0,失败返回EOF
/* 字符逐字复制 */ char ch; while ((ch = fgetc(fp_in)) != EOF) { fputc(ch, fp_out); }
/* 字符串逐行复制 */ char line[256]; while (fgets(line, 256, fp_in) != NULL) { fputs(line, fp_out); }
⚠️ 注意
fgets 读到 n-1 个字符或遇到换行符或 EOF 就停止,并自动加上 '\0' 结束符。如果读到换行符,换行符也会被读入字符串中。

10.4.4 · FORMATTED I/O

📊 fprintf / fscanf 格式化读写
/* fprintf — 写入格式化数据 */ FILE *fp = fopen("score.txt", "w"); int id = 1001; char name[] = "张三"; float score = 92.5; fprintf(fp, "%d %s %.1f\n", id, name, score); // score.txt 内容: // 1001 张三 92.5
/* fscanf — 读取格式化数据 */ FILE *fp = fopen("score.txt", "r"); int id; char name[20]; float score; while (fscanf(fp, "%d %s %f", &id, name, &score) != EOF) { printf("%d: %s - %.1f\n", id, name, score); }
💡 对比:printf/scanf vs fprintf/fscanf
printf(...) ≡ fprintf(stdout, ...)
scanf(...) ≡ fscanf(stdin, ...)
— 标准I/O即特殊的文件I/O(stdin/stdout/stderr 是三个自动打开的FILE*)

10.4.5 · BINARY R/W

💾 fread / fwrite 二进制读写  
/* fwrite — 二进制写入 */ struct Student { int id; char name[20]; float score; } stu = {1001, "张三", 92.5}; fp = fopen("stu.dat", "wb"); fwrite(&stu, sizeof(struct Student), 1, fp);
/* fread — 二进制读取 */ struct Student stu; fp = fopen("stu.dat", "rb"); while (fread(&stu, sizeof(struct Student), 1, fp) == 1) { printf("%d %s %.1f\n", stu.id, stu.name, stu.score); }

🛠️ 交互式代码编辑器 — 修改下方代码并运行模拟

// 在此编辑 C 代码示例,模拟文件读写逻辑 struct Student { int id; char name[20]; float score; }; // 写入3个学生 struct Student list[3] = { {1001, "张三", 92.5}, {1002, "李四", 88.0}, {1003, "王五", 76.5} }; FILE *fp = fopen("stu.dat", "wb"); fwrite(list, sizeof(struct Student), 3, fp); fclose(fp); // 读取并打印 fp = fopen("stu.dat", "rb"); struct Student s; while (fread(&s, sizeof(struct Student), 1, fp) == 1) printf("%d %s %.1f\n", s.id, s.name, s.score); fclose(fp);
// 点击"模拟执行"查看输出...

10.5.1 · FSEEK

🎯 fseek 文件定位
/* 函数原型 */ int fseek(FILE *fp, long offset, int whence); /* whence 起始点 */ SEEK_SET 0 // 文件开头 SEEK_CUR 1 // 当前位置 SEEK_END 2 // 文件末尾 /* 示例 */ fseek(fp, 100L, SEEK_SET); // → 从开头移100字节 fseek(fp, 50L, SEEK_CUR); // → 从当前位置后移50 fseek(fp, -10L, SEEK_END); // → 从末尾向前10字节
/* === fseek 定位示意 === */ 文件开头(SEEK_SET=0) │ │ fseek(fp, 100L, SEEK_SET) ▼ ├── 0 字节 ├── ... ├── 100 ◄── 读写指针移到这里 ├── ... ├── 当前位置(SEEK_CUR=1) │ │ │ │ fseek(fp, 50L, SEEK_CUR) │ ▼ ├── +50 ◄── 后移50字节 ├── ... ├── 末尾(SEEK_END=2) │ │ │ │ fseek(fp, -10L, SEEK_END) │ ▼ └── 倒数第10字节 ◄──

10.5.2-10.5.3 · FTELL & REWIND

📍 ftell / rewind 位置查询与重置
/* ftell — 获取当前读写位置 */ long ftell(FILE *fp); /* 常用于获取文件大小 */ fseek(fp, 0L, SEEK_END); // 移到末尾 long size = ftell(fp); // 获取字节数 /* rewind — 重置到开头 */ void rewind(FILE *fp); // 等价于: // fseek(fp, 0L, SEEK_SET);
/* === 获取文件大小模板 === */ long getFileSize(const char *filename) { FILE *fp = fopen(filename, "rb"); if (!fp) return -1; fseek(fp, 0L, SEEK_END); // 跳到末尾 long size = ftell(fp); // 记录位置 fclose(fp); return size; }

10.6 · ERROR DETECTION

🔍 ferror / feof 出错检测
函数功能返回值清除
ferror(fp) 检查文件操作是否出错 出错返回非0,正常返回0 clearerr(fp)
feof(fp) 检查是否读到文件末尾 到末尾返回非0,未到返回0 clearerr(fp)
/* 读写错误检测模式 */ while (!feof(fp)) { ch = fgetc(fp); if (ferror(fp)) { printf("读取出错!\n"); clearerr(fp); break; } putchar(ch); }
⚠️ 常见误区
不要用 feof(fp) 作为循环终止条件去读文件!

正确做法:用读函数的返回值判断
while(fgets(buf, n, fp) != NULL)

原因:feof在读取失败后才返回真,会多读一次。

10.7 · PROJECT

🏗️ 综合实战:学生成绩管理系统
/* ① 将键盘输入的学生数据写入文件 */ #define N 5 struct Student { int id; char name[20]; float s[3]; } stu[N]; FILE *fp; int i; if ((fp = fopen("stu.dat", "wb")) == NULL) { exit(1); } for (i = 0; i < N; i++) { printf("学号 姓名 成绩1 成绩2 成绩3: "); scanf("%d %s %f %f %f", &stu[i].id, stu[i].name, &stu[i].s[0], &stu[i].s[1], &stu[i].s[2]); } fwrite(stu, sizeof(struct Student), N, fp); fclose(fp);
/* ② 从文件读取学生数据,计算平均分 */ struct Student s; float avg; fp = fopen("stu.dat", "rb"); printf("学号\t姓名\t均分\n"); while (fread(&s, sizeof(struct Student), 1, fp) == 1) { avg = (s.s[0] + s.s[1] + s.s[2]) / 3.0; printf("%d\t%s\t%.1f\n", s.id, s.name, avg); } fclose(fp);
/* ③ 修改第k个学生的数据 */ struct Student s_new = {1005, "赵六", {88, 92, 85}}; int k = 2; // 修改第2个(从0计数) fp = fopen("stu.dat", "rb+"); fseek(fp, k * sizeof(struct Student), SEEK_SET); fwrite(&s_new, sizeof(struct Student), 1, fp); fclose(fp);

📝 QUIZ

🧠 随堂测验(10题)
1. 要以"只读"方式打开文本文件,应使用哪个模式?
2. fclose(fp) 返回什么值表示成功?
3. fread(buffer, size, count, fp) 返回什么?
4. 使用 fseek(fp, 0L, SEEK_END) 之后,ftell(fp) 返回什么?
5. 以 "a" 模式打开文件时,文件读写指针在什么位置?
6. fflush(fp) 的作用是什么?
7. fprintf(fp, "%d", 123) 写入文件的是?
8. rewind(fp) 等价于?
9. 二进制文件比ASCII文件更小的原因?
10. 获取文件大小,正确步骤是?
得分:0/10