CC语言通关
第11课 · 要写代码
教材对照
第11课配套 · 纯代码

第11课 · 要写代码

FILE*、fopen/fclose、顺序读写、随机读写、出错检测

22 段代码每行都有注释边敲边想
教材依据第10章《对文件的输入输出》书页 p.331–356
本课覆盖:教材第10章正文 + 书末习题(p.355–356)。标「教材 p.X」= 教材段落总结;「例题」= 教材例题;「习题」= 书末题目;「本课补充」= 非教材原文。
在全书对照表中查看 →

本章的代码有一个统一的"骨架":定义 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;
}

四、加练题

(暂无。想加练就说"再出几道文件题",我出的题会标上日期追加在这里。)