【C语言-文件操作】4. 综合实战——用文件操作写一个真正的持久化程序

开发技术 作者: 精灵星空 SKY315.COM 更新于: 浏览: 7
码字发文不易,转载请注明出处【精灵星空 SKY315.COM】

前三节课咱们把文件操作的基础都讲完了——打开关闭、四种读写方式、定位和状态检测。但知识这东西,光学不练等于白学。今天这节课,咱们要把前面学的东西全串起来,写一个真正能用的小项目。

这个项目就是学生信息管理系统。你可能会说“这玩意儿我写过”——但之前你写的多半是数据存在数组里,程序一关就全没了。今天咱们要给它加上文件持久化,让数据真正“记住”,下次打开程序还在。

这就是文件操作最核心的价值:让程序有记忆。

先想清楚要做什么

咱们要写一个学生信息管理系统,功能就四个:

  1. 添加学生——输入学号、姓名、成绩,存进去。

  2. 显示所有学生——把已经存的学生信息都打出来。

  3. 保存到文件——把内存里的学生数据写进硬盘文件。

  4. 从文件加载——程序启动的时候,把之前存的数据读回来。

程序运行起来长这样:

===== 学生信息管理系统 =====
1. 添加学生
2. 显示所有学生
3. 保存到文件
4. 从文件加载
0. 退出
请选择:

用户输入数字选功能,干完活回到菜单,直到选0退出。

数据结构怎么定?

每个学生有学号、姓名、成绩,咱们用结构体打包:

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

整个系统最多能存多少个学生?咱们定个上限,比如100个。用一个结构体数组存所有学生,再用一个变量记录当前存了多少个:

#define MAX_STUDENTS 100

Student students[MAX_STUDENTS];
int count = 0;   // 当前学生人数

文件用哪种方式存?

存文件有两种选择:文本方式和二进制方式。

文本方式的好处是你用记事本就能打开看,调试方便。但读取的时候格式必须严格匹配,文件被手动改过就可能读不出来。

二进制方式的好处是存取快、精度不丢,结构体直接写直接读,代码简单。缺点是打开文件是乱码,没法手动检查。

咱们这个项目,我选二进制方式。原因很简单——学生信息是结构体,用fwrite和fread直接整块存取,代码干净利落,不用一个个字段去格式化。

功能一:添加学生

void add_student() {
    if (count >= MAX_STUDENTS) {
        printf("人数已满,无法添加\n");
        return;
    }

    Student s;
    printf("请输入学号:");
    scanf("%d", &s.id);
    printf("请输入姓名:");
    scanf("%s", s.name);
    printf("请输入成绩:");
    scanf("%f", &s.score);

    students[count] = s;
    count++;
    printf("添加成功!当前共%d名学生\n", count);
}

这个函数就是往数组末尾加一条记录。简单的很。

功能二:显示所有学生

void show_all() {
    if (count == 0) {
        printf("暂无学生信息\n");
        return;
    }

    printf("\n%-10s %-15s %-10s\n", "学号", "姓名", "成绩");
    printf("--------------------------------------\n");
    for (int i = 0; i < count; i++) {
        printf("%-10d %-15s %-10.1f\n",
               students[i].id, students[i].name, students[i].score);
    }
    printf("共%d名学生\n", count);
}

%-10s的意思是左对齐占10个字符宽度,打印出来整整齐齐的表格。

功能三:保存到文件

这是今天的新东西。用fwrite把整个数组一次性写进文件:

void save_to_file() {
    FILE *fp = fopen("students.dat", "wb");
    if (fp == NULL) {
        perror("保存失败");
        return;
    }

    // 先写学生人数
    fwrite(&count, sizeof(int), 1, fp);
    // 再写所有学生数据
    fwrite(students, sizeof(Student), count, fp);

    fclose(fp);
    printf("已保存%d名学生到文件\n", count);
}

拆开看:

  • fopen("students.dat", "wb")——二进制写模式打开。

  • fwrite(&count, sizeof(int), 1, fp)——先把学生人数写进去。为啥要先写人数?因为读的时候你得知道要读多少个。

  • fwrite(students, sizeof(Student), count, fp)——把整个数组的count个元素一次性写进去。

  • fclose(fp)——关文件。

注意:写的时候一定要把count也写进去。 如果不写,读的时候你不知道数组里实际有多少个有效数据,只能把整个数组100个元素全读出来,后面全是垃圾数据。

功能四:从文件加载

保存是写进去,加载就是读回来。用fread:

void load_from_file() {
    FILE *fp = fopen("students.dat", "rb");
    if (fp == NULL) {
        printf("没有找到存档文件,可能是第一次使用\n");
        return;
    }

    // 先读学生人数
    fread(&count, sizeof(int), 1, fp);
    // 再读所有学生数据
    fread(students, sizeof(Student), count, fp);

    fclose(fp);
    printf("已从文件加载%d名学生\n", count);
}

拆开看:

  • fopen("students.dat", "rb")——二进制读模式打开。

  • 文件不存在的时候,fopen返回NULL,说明是第一次用,还没有存档。

  • fread(&count, sizeof(int), 1, fp)——先读人数。

  • fread(students, sizeof(Student), count, fp)——根据人数读对应数量的学生数据。

  • fclose(fp)——关文件。

读和写的顺序必须一致——先写的人数,读的时候也先读人数。先写的数组,读的时候也先读数组。顺序反了,数据全乱。

主函数——把菜单搭起来

int main() {
    int choice;

    // 启动时自动加载
    load_from_file();

    while (1) {
        printf("\n===== 学生信息管理系统 =====\n");
        printf("1. 添加学生\n");
        printf("2. 显示所有学生\n");
        printf("3. 保存到文件\n");
        printf("4. 从文件加载\n");
        printf("0. 退出\n");
        printf("请选择:");
        scanf("%d", &choice);

        switch (choice) {
            case 1:
                add_student();
                break;
            case 2:
                show_all();
                break;
            case 3:
                save_to_file();
                break;
            case 4:
                load_from_file();
                break;
            case 0:
                printf("退出前自动保存...\n");
                save_to_file();
                printf("再见!\n");
                return 0;
            default:
                printf("无效选择,请重新输入\n");
        }
    }

    return 0;
}

主函数里做了两件事:

第一,程序启动时自动调用load_from_file。这样你上次存的数据,这次打开就能直接看到。

第二,退出前自动调用save_to_file。这样你退出程序的时候不用手动保存,数据自动就存好了。

这就是“持久化”的完整体验——用户啥都不用操心,数据自动记住。

完整代码

把上面的函数拼在一起,就是完整的程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_STUDENTS 100
#define FILE_NAME "students.dat"

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

Student students[MAX_STUDENTS];
int count = 0;

void add_student() {
    if (count >= MAX_STUDENTS) {
        printf("人数已满,无法添加\n");
        return;
    }

    Student s;
    printf("请输入学号:");
    scanf("%d", &s.id);
    printf("请输入姓名:");
    scanf("%s", s.name);
    printf("请输入成绩:");
    scanf("%f", &s.score);

    students[count] = s;
    count++;
    printf("添加成功!当前共%d名学生\n", count);
}

void show_all() {
    if (count == 0) {
        printf("暂无学生信息\n");
        return;
    }

    printf("\n%-10s %-15s %-10s\n", "学号", "姓名", "成绩");
    printf("--------------------------------------\n");
    for (int i = 0; i < count; i++) {
        printf("%-10d %-15s %-10.1f\n",
               students[i].id, students[i].name, students[i].score);
    }
    printf("共%d名学生\n", count);
}

void save_to_file() {
    FILE *fp = fopen(FILE_NAME, "wb");
    if (fp == NULL) {
        perror("保存失败");
        return;
    }

    fwrite(&count, sizeof(int), 1, fp);
    fwrite(students, sizeof(Student), count, fp);

    fclose(fp);
    printf("已保存%d名学生到文件\n", count);
}

void load_from_file() {
    FILE *fp = fopen(FILE_NAME, "rb");
    if (fp == NULL) {
        printf("没有找到存档文件,可能是第一次使用\n");
        return;
    }

    fread(&count, sizeof(int), 1, fp);
    fread(students, sizeof(Student), count, fp);

    fclose(fp);
    printf("已从文件加载%d名学生\n", count);
}

int main() {
    int choice;

    load_from_file();

    while (1) {
        printf("\n===== 学生信息管理系统 =====\n");
        printf("1. 添加学生\n");
        printf("2. 显示所有学生\n");
        printf("3. 保存到文件\n");
        printf("4. 从文件加载\n");
        printf("0. 退出\n");
        printf("请选择:");
        scanf("%d", &choice);

        switch (choice) {
            case 1: add_student(); break;
            case 2: show_all(); break;
            case 3: save_to_file(); break;
            case 4: load_from_file(); break;
            case 0:
                printf("退出前自动保存...\n");
                save_to_file();
                printf("再见!\n");
                return 0;
            default:
                printf("无效选择,请重新输入\n");
        }
    }

    return 0;
}

跑起来看看效果

编译运行,第一次打开:

没有找到存档文件,可能是第一次使用
===== 学生信息管理系统 =====
1. 添加学生
...

选1,添加三个学生:

请输入学号:101
请输入姓名:张三
请输入成绩:85.5
添加成功!当前共1名学生

选2显示:

学号       姓名            成绩
--------------------------------------
101        张三            85.5
共1名学生

选0退出,程序自动保存。关掉程序,再重新打开:

已从文件加载1名学生
===== 学生信息管理系统 =====
...

数据还在!这就是持久化的效果。

可以继续扩展的方向:

这个程序虽然简单,但麻雀虽小五脏俱全——菜单、结构体、数组、文件读写、增删查改的框架都齐了。你可以在此基础上加更多功能:

  • 按学号查找:遍历数组,找到学号匹配的打印出来。

  • 按学号删除:找到后把后面的元素往前挪,count--。

  • 修改学生信息:找到后重新输入。

  • 按成绩排序:用冒泡或者选择排序对数组排序。

  • 统计信息:算平均分、最高分、最低分。

  • 多文件存储:每个学生存一个文件,或者按班级分文件存。

每加一个功能,你都是在用之前学过的知识(循环、分支、数组、结构体、函数)配合文件操作来解决问题。这就是综合实战的意义——把所有零散的知识点串成一条线,做出一个真正有用的东西。

几个常见的坑:

坑1:fwrite和fread的顺序不一致

写的时候是先写count再写数组,读的时候也必须先读count再读数组。顺序反了,读出来的数据全乱套。

坑2:打开文件失败没处理

fopen返回NULL的时候,你后面用fwrite或fread就会崩溃。必须检查。

坑3:忘记关闭文件

fwrite写完之后不fclose,数据可能还在缓冲区里没真正写到硬盘。程序被强制关闭的时候,数据就丢了。

坑4:二进制文件跨平台问题

你在Windows上存的二进制文件,拿到Linux上可能读不出来(或者读出来是错的),因为不同平台的字节序和结构体对齐方式可能不同。你自己电脑上用没问题,但要注意这个局限。

总结一下:

这节课咱们把文件操作的所有知识串起来,做了一个能持久化的学生管理系统。核心流程就是:

启动时加载 → 用户操作内存数据 → 退出时保存

文件操作在实际开发里就是这么用的。你以后写的任何需要“记住”数据的程序——游戏存档、配置管理、日志记录、数据库——本质上都是这个套路。

回去自己动手,把代码敲一遍跑一遍。然后试着加一个“按学号查找”的功能,再试着加一个“删除学生”的功能。改着改着,你不仅文件操作会了,整个C语言的知识都会在你的脑子里连成一片。