程序跑完就消失的数据没有意义,把结果存进文件、从文件读入配置才是常态。C 用 FILE * 完成文件读写,流程永远是四步:打开 → 操作 → 检查 → 关闭。

1. 文件指针与 fopen

所有文件操作都围绕 FILE * 进行。fopen(路径, 模式) 打开文件,模式决定"读还是写":

模式含义文件不存在时
"r"只读失败返回 NULL
"w"只写,清空原内容自动创建
"a"追加写自动创建
"r+" / "w+"读写"w+" 会清空
"rb" / "wb"二进制读写

fopen 失败返回 NULL,必须检查,否则解引用 NULL 会崩溃。

2. 写入文件

写文本用 fprintf(格式化)或 fputs(整行):

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("notes.txt", "w");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }

    fprintf(fp, "Hello, C File!\n");
    fprintf(fp, "第 %d 行: 今天写了 %d 行代码\n", 2, 120);
    fputs("fputs 写的最后一行\n", fp);

    fclose(fp);
    printf("写入完成\n");
    return 0;
}

写完务必 fclose:缓冲区里的内容要等关闭(或 flush)才会真正落盘。

3. 读取文件

逐行读取用 fgets,它自带边界保护,不会像 gets 那样溢出;格式化读取用 fscanf:

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("notes.txt", "r");
    if (fp == NULL) {
        printf("文件不存在\n");
        return 1;
    }

    char line[100];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("读到: %s", line);   // fgets 会保留换行符
    }
    fclose(fp);
    return 0;
}

读结构化数据时 fscanf 更方便,但它对格式错误敏感,生产代码建议配合 fgets + sscanf 使用。

4. 二进制文件

结构体等内存数据直接落盘用 fwrite/fread,按"块"读写:

#include <stdio.h>

struct Record {
    int id;
    double value;
};

int main(void) {
    struct Record recs[2] = {{1, 3.14}, {2, 2.71}};

    FILE *fp = fopen("data.bin", "wb");
    fwrite(recs, sizeof(struct Record), 2, fp);
    fclose(fp);

    struct Record readback[2];
    fp = fopen("data.bin", "rb");
    fread(readback, sizeof(struct Record), 2, fp);
    fclose(fp);

    printf("读回: id=%d value=%.2f\n", readback[1].id, readback[1].value);
    return 0;
}

二进制文件读写快、无格式转换开销,但跨平台、跨编译器时要注意结构体对齐和字节序问题。

5. 错误处理与文件结尾

文件操作必须处理两类情况:打开失败读到结尾。用 feof 判断是否到文件尾:

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("scores.txt", "r");
    if (fp == NULL) {
        perror("打开失败");        // 打印具体错误原因
        return 1;
    }

    int score, count = 0, sum = 0;
    while (fscanf(fp, "%d", &score) == 1) {  // 读到才继续
        sum += score;
        count++;
    }
    if (feof(fp)) {
        printf("平均 %.1f\n", sum / (double)count);
    }
    fclose(fp);
    return 0;
}

判断读取循环的终止条件,用 fscanf返回值feof 更可靠——feof 只在尝试读过结尾之后才为真。

把读写能力串起来,用 1KB 缓冲区批量搬运文件——缓冲区大小正是文件 IO 性能的关键:

💡 学习建议:做一个"记账本"小程序:支持添加记录、保存到文件、下次启动时读入。把文件读写、结构体、循环全用上,比背十个 API 都有效。