本章的代码有一个统一的"骨架":定义 FILE * → fopen(判空)→ 读写 → fclose。
下面每一行都带//注释;先照着敲一遍,再合上书自己写一遍。所有程序都在 Visual Studio 2022(C11)下编译通过。
一、知识点代码
c
// ============ 1. FILE* / fopen / fclose:文件操作的最小骨架 ============
#include <stdio.h> // 需要 FILE、fopen、fclose、fprintf
#include <stdlib.h> // 需要 exit
int main(void) // 主函数,程序入口
{
FILE *fp; // 定义一个"文件指针",用它握住文件
fp = fopen("demo1.txt", "w"); // 以"只写"方式打开:不存在就新建,存在就清空
if (fp == NULL) // fopen 失败会返回空指针 NULL
{
printf("cannot open file\n"); // 打不开就提示
exit(0); // 终止程序(要用 stdlib.h)
}
fprintf(fp, "hello file\n"); // 往文件里写一行字(格式化写,10.3.3 讲)
fclose(fp); // 关闭文件:把缓冲区剩余数据写盘,再断开
printf("done\n"); // 屏幕上提示一句
return 0; // 程序正常结束
}c
// ============ 2. fputc / fgetc:一个字符一个字符地搬 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch; // 必须是 int:要能装下 EOF(-1)
fp = fopen("demo2.txt", "w"); // 只写打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空,失败就退出
fputc('A', fp); // 写一个字符 A 到文件
fputc('B', fp); // 再写一个字符 B
fputc('\n', fp); // 写一个换行符
fclose(fp); // 关闭,保证内容真的落盘
fp = fopen("demo2.txt", "r"); // 重新以"只读"方式打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 再判空
while ((ch = fgetc(fp)) != EOF) // 读一个字符,读到文件尾(EOF)就停
putchar(ch); // 把读到的字符显示到屏幕
fclose(fp); // 关闭文件
return 0; // 结束
}c
// ============ 3. fgets / fputs:一次搬一个字符串 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
char str[20]; // 字符数组,当"接货的筐"
fp = fopen("demo3.txt", "w"); // 只写打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fputs("China\n", fp); // 写字符串,换行要自己写进 \n
fputs("Beijing\n", fp); // 再写一行
fclose(fp); // 关闭
fp = fopen("demo3.txt", "r"); // 只读打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
while (fgets(str, 20, fp) != NULL) // 最多读 19 个字符,成功返回 str
printf("%s", str); // fgets 已把 '\n' 读进来,这里不用再加
fclose(fp); // 关闭
return 0; // 结束
}c
// ============ 4. fprintf / fscanf:带格式的读写 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int num; // 存放学号
float score; // 存放成绩
char name[20]; // 存放姓名
fp = fopen("demo4.txt", "w"); // 只写打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fprintf(fp, "%d %.2f %s\n", 1001, 92.5, "Zhang"); // 按格式写入文件
fclose(fp); // 关闭
fp = fopen("demo4.txt", "r"); // 只读打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fscanf(fp, "%d %f %s", &num, &score, name); // 按同样格式读回
printf("%d %.2f %s\n", num, score, name); // 输出验证
fclose(fp); // 关闭
return 0; // 结束
}c
// ============ 5. fread / fwrite:整块原样搬运(二进制)============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
struct Student // 学生类型
{
int num; // 学号
char name[10]; // 姓名
float score; // 成绩
};
int main(void) // 主函数
{
FILE *fp; // 文件指针
struct Student a = {1001, "Zhang", 92.5f}; // 内存里的一个学生
struct Student b; // 用来接读回来的数据
fp = fopen("demo5.dat", "wb"); // 二进制只写(wb)
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fwrite(&a, sizeof(struct Student), 1, fp); // 把 a 整块写进文件
fclose(fp); // 关闭
fp = fopen("demo5.dat", "rb"); // 二进制只读(rb)
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fread(&b, sizeof(struct Student), 1, fp); // 整块读回到 b
printf("%d %s %.1f\n", b.num, b.name, b.score); // 输出验证
fclose(fp); // 关闭
return 0; // 结束
}c
// ============ 6. fseek / ftell / rewind:把"取货员"送到指定位置 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
long pos; // ftell 返回 long
fp = fopen("demo6.txt", "w"); // 只写打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fputs("ABCDEFGHIJ", fp); // 写入 10 个字符
fclose(fp); // 关闭
fp = fopen("demo6.txt", "rb"); // 用二进制读,位置和字节一一对应
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fseek(fp, 0L, SEEK_END); // 位置移到文件末尾
pos = ftell(fp); // 报告当前位置,就是文件长度
printf("size=%ld\n", pos); // 输出 10
rewind(fp); // 位置回到文件开头
printf("first=%c\n", fgetc(fp)); // 读第 1 个字符 A
fseek(fp, 4L, SEEK_SET); // 从开头数 4 个字节,到第 5 个字符
printf("fifth=%c\n", fgetc(fp)); // 读 E
fseek(fp, -1L, SEEK_END); // 从末尾后退 1 个字节
printf("last=%c\n", fgetc(fp)); // 读 J
fclose(fp); // 关闭
return 0; // 结束
}c
// ============ 7. feof / ferror / clearerr:文件尾与出错检测 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch, n = 0; // ch 存字符,n 计数
fp = fopen("demo7.txt", "w"); // 只写打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fputs("abc", fp); // 写入 3 个字符
fclose(fp); // 关闭
fp = fopen("demo7.txt", "r"); // 只读打开
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
while (1) // 先读、再判断
{
ch = fgetc(fp); // 读一个字符
if (ch == EOF) break; // 读到文件尾或出错,跳出
putchar(ch); // 显示
n++; // 计数
}
printf("\nn=%d feof=%d ferror=%d\n", n, feof(fp), ferror(fp)); // feof=1, ferror=0
clearerr(fp); // 清除结束标志和出错标志
printf("after clear: feof=%d ferror=%d\n", feof(fp), ferror(fp)); // 都变 0
fclose(fp); // 关闭
return 0; // 结束
}c
// ============ 8. 文本方式 vs 二进制方式:同一个数占几个字节 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int x = 12345; // 要写出去的数
long textSize, binSize; // 两种方式占的字节数
fp = fopen("demo8.txt", "w"); // 文本方式写
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fprintf(fp, "%d", x); // 按字符写:'1''2''3''4''5'
fclose(fp); // 关闭
fp = fopen("demo8.dat", "wb"); // 二进制方式写
if (fp == NULL) { printf("open error\n"); exit(0); } // 判空
fwrite(&x, sizeof(int), 1, fp); // 把 4 字节整数原样搬进文件
fclose(fp); // 关闭
fp = fopen("demo8.txt", "rb"); // 用 rb 打开,只为量长度
fseek(fp, 0L, SEEK_END); // 到文件末尾
textSize = ftell(fp); // 量出字节数
fclose(fp); // 关闭
fp = fopen("demo8.dat", "rb"); // 再量二进制文件
fseek(fp, 0L, SEEK_END); // 到文件末尾
binSize = ftell(fp); // 量出字节数
fclose(fp); // 关闭
printf("text=%ld binary=%ld\n", textSize, binSize); // text=5 binary=4
return 0; // 结束
}二、课后题 · 代码题
c
// ============ 习题 3:小写转大写,存到文件 test,遇 ! 结束 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch; // 存放读到的字符
fp = fopen("test", "w"); // 打开(或新建)文件 test
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
printf("请输入字符串,以 ! 结束:\n"); // 提示
while ((ch = getchar()) != '!' && ch != EOF) // 读到 ! 停;EOF 也停,防止死循环
{
if (ch >= 'a' && ch <= 'z') // 如果是小写字母
ch = ch - 32; // 小写和大写差 32,减 32 变大写
fputc(ch, fp); // 把字符写进文件
}
fclose(fp); // 关闭文件
printf("\nsaved to test\n"); // 屏幕提示
return 0; // 结束
}c
// ============ 习题 5:5 个学生 3 门课,算平均分,存进 stud 文件 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
#define N 5 // 学生人数
struct Student // 学生类型
{
int num; // 学号
char name[20]; // 姓名
float score[3]; // 3 门课成绩
float avg; // 平均分
};
int main(void) // 主函数
{
FILE *fp; // 文件指针
struct Student stu[N]; // 5 个学生的数组
int i; // 循环变量
for (i = 0; i < N; i++) // 逐个输入
{
scanf("%d %s %f %f %f", &stu[i].num, stu[i].name,
&stu[i].score[0], &stu[i].score[1], &stu[i].score[2]); // 读一个学生的三项数据
stu[i].avg = (stu[i].score[0] + stu[i].score[1] + stu[i].score[2]) / 3.0f; // 算平均分
}
fp = fopen("stud", "wb"); // 二进制只写打开 stud
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
for (i = 0; i < N; i++) // 逐个写
fwrite(&stu[i], sizeof(struct Student), 1, fp); // 一次写一个学生
fclose(fp); // 关闭
printf("saved to stud\n"); // 提示
return 0; // 结束
}c
// ============ 习题 11:输入若干行存文件,再读回把小写转大写输出 ============
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
char line[256]; // 一行字符的筐
int i; // 循环变量
fp = fopen("lines.txt", "w"); // 只写打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
printf("输入若干行,直接回车(空行)结束:\n"); // 提示
while (fgets(line, 256, stdin) != NULL) // 从键盘读一行(含换行)
{
if (line[0] == '\n') break; // 空行就结束输入
fputs(line, fp); // 整行写进文件
}
fclose(fp); // 关闭
fp = fopen("lines.txt", "r"); // 只读重新打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
while (fgets(line, 256, fp) != NULL) // 从文件读回一行
{
for (i = 0; line[i] != '\0'; i++) // 逐个字符扫描
if (line[i] >= 'a' && line[i] <= 'z') // 是小写字母
line[i] = line[i] - 32; // 转成大写
printf("%s", line); // 输出这一行
}
fclose(fp); // 关闭
return 0; // 结束
}三、我出的题 · 随课练习
🔮 猜输出
c
// 猜输出 1:先想清楚文件里有什么、位置标记停在哪
#include <stdio.h> // 标准输入输出
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch, n = 0; // ch 存字符,n 计数
fp = fopen("g1.txt", "w"); // 只写打开
fputs("12345", fp); // 写入 5 个字符
fclose(fp); // 关闭
fp = fopen("g1.txt", "r"); // 只读打开
fseek(fp, 2L, SEEK_SET); // 位置标记移到第 3 个字节
while ((ch = fgetc(fp)) != EOF) // 逐字符读
{
putchar(ch); // 输出
n++; // 计数
}
printf(" %d\n", n); // 输出计数
fclose(fp); // 关闭
return 0; // 结束
}c
// 猜输出 2:数字被原样写进二进制文件,再原样读回
#include <stdio.h> // 标准输入输出
int main(void) // 主函数
{
FILE *fp; // 文件指针
int a = 65; // 初值 65
fp = fopen("g2.dat", "wb"); // 二进制只写
fwrite(&a, sizeof(int), 1, fp); // 整块写
fclose(fp); // 关闭
a = 0; // 故意清零,看读回是否恢复
fp = fopen("g2.dat", "rb"); // 二进制只读
fread(&a, sizeof(int), 1, fp); // 整块读
fclose(fp); // 关闭
printf("%d %c\n", a, a); // 按整数和字符各输出一次
return 0; // 结束
}c
// 猜输出 3:ftell 报的是"下一个要读的字节"的位置
#include <stdio.h> // 标准输入输出
int main(void) // 主函数
{
FILE *fp; // 文件指针
long p1, p2; // 两个位置
fp = fopen("g3.txt", "w"); // 只写打开
fputs("ABCDEF", fp); // 写入 6 个字符
fclose(fp); // 关闭
fp = fopen("g3.txt", "r"); // 只读打开
fgetc(fp); // 读 1 个字符
fgetc(fp); // 再读 1 个字符
p1 = ftell(fp); // 记下当前位置
fseek(fp, -2L, SEEK_END); // 从末尾后退 2 个字节
p2 = ftell(fp); // 记下当前位置
printf("%ld %ld %c\n", p1, p2, fgetc(fp)); // 输出两个位置和读到的字符
fclose(fp); // 关闭
return 0; // 结束
}c
// 猜输出 4:fprintf 写进去的字符,fscanf 按格式读回来
#include <stdio.h> // 标准输入输出
int main(void) // 主函数
{
FILE *fp; // 文件指针
int a = 7, x = 0; // 写入值和读回值
float b = 3.5f, y = 0; // 写入值和读回值
fp = fopen("g4.txt", "w"); // 只写打开
fprintf(fp, "%d %.1f", a, b); // 按格式写入
fclose(fp); // 关闭
fp = fopen("g4.txt", "r"); // 只读打开
fscanf(fp, "%d %f", &x, &y); // 按格式读回
fclose(fp); // 关闭
printf("%d %.1f\n", x, y); // 输出读回的值
return 0; // 结束
}🛠 变式题
c
// 变式 1(照猫画虎):统计文件 in.txt 一共有多少个字符
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch, count = 0; // ch 存字符,count 计数
fp = fopen("in.txt", "r"); // 只读打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
/* TODO 1:写一个 while 循环,用 fgetc 逐字符读,每读一个 count++ */
/* TODO 2:循环结束后 fclose(fp) */
printf("count=%d\n", count); // 输出总数
return 0; // 结束
}c
// 变式 2(改需求):结束符从 '#' 改成 '@',文件名改为运行时键盘输入
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int ch; // 存放字符
char filename[40]; // 存放文件名
printf("请输入文件名:"); // 提示
scanf("%s", filename); // 读文件名
getchar(); // 消化掉 scanf 留下的回车
fp = fopen(filename, "w"); // 按输入的名字打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
/* TODO:把下面循环条件里的 '#' 改成题目要求的结束符 */
while ((ch = getchar()) != '#') // 读字符直到结束符
{
fputc(ch, fp); // 写进文件
}
fclose(fp); // 关闭
return 0; // 结束
}c
// 变式 3(翻译):把伪代码翻成 C —— 从 data.txt 读 10 个整数求和
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
int i, x, sum = 0; // i 计数,x 读数,sum 累加
fp = fopen("data.txt", "r"); // 只读打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
/* 伪代码:for i = 1 to 10 { 读一个整数 x; sum = sum + x } */
/* TODO:用 fscanf(fp, "%d", &x) 完成这个循环 */
fclose(fp); // 关闭
printf("sum=%d\n", sum); // 输出和
return 0; // 结束
}c
// 变式 4(补全):先用 fputs 写两行,再用 fgets 循环读回
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit 用
int main(void) // 主函数
{
FILE *fp; // 文件指针
char s[50]; // 一行字符的筐
fp = fopen("note.txt", "w"); // 只写打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
fputs("hello\n", fp); // 写第一行
/* TODO 1:再写第二行 "world"(别忘了换行) */
fclose(fp); // 关闭
fp = fopen("note.txt", "r"); // 只读打开
if (fp == NULL) { printf("cannot open\n"); exit(0); } // 判空
/* TODO 2:用 while (fgets(s, 50, fp) != NULL) 循环读,printf("%s", s) 输出 */
fclose(fp); // 关闭
return 0; // 结束
}🐛 改错题
c
// ❌ 错误示例(故意)
// 错 1:fopen 之后没有判断 fp 是否为 NULL
// 错 2:文件不存在时 fp 是空指针,fgetc(fp) 会直接崩溃
// 改法:在 fopen 后加 if (fp == NULL) { printf("cannot open\n"); return 1; }
#include <stdio.h>
int main(void)
{
FILE *fp;
int ch;
fp = fopen("no_such_file.txt", "r");
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}c
// ❌ 错误示例(故意)
// 错:用 char 型变量接 fgetc 的返回值;若 char 是无符号的,永远等不到 -1,会死循环
// 改法:把 char ch; 改成 int ch;
#include <stdio.h>
int main(void)
{
FILE *fp;
char ch;
fp = fopen("e2.txt", "r");
if (fp == NULL) return 1;
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}c
// ❌ 错误示例(故意)
// 错:字符串里的 '\d' 和 '\a' 是转义字符,路径被改写
// 改法:写成 "D:\\data\\a.txt",字符串中的反斜杠要写两个
#include <stdio.h>
int main(void)
{
FILE *fp;
fp = fopen("D:\data\a.txt", "r");
if (fp == NULL) { printf("cannot open\n"); return 1; }
fclose(fp);
return 0;
}四、加练题
(暂无。想加练就说"再出几道文件题",我出的题会标上日期追加在这里。)