news 2026/10/20 2:10:11

4-5 文件读写:能保存数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
4-5 文件读写:能保存数据

4-5 文件读写:能保存数据

⏱ 本节预计用时:40分钟

本节意义:让你的软件能保存数据!所有软件必用!

本节目标:理解为什么要文件读写;学会fopen/fprintf/fscanf/fclose四个函数,给学生管理系统加上保存/加载功能。


1 怎么让程序关了数据还在

简单说:你现在所有学生都存在内存里。程序退出,操作系统就把这块内存收回给别的程序用了——下次启动,是一块全新的空内存。

要让数据"关了也在",就得把它存在磁盘上。即便程序退出了,数据依然躺在那里。

存在磁盘上的数据就是"文件",本质上就是磁盘上一串二进制数。

你想把学生存进去,就得把学生信息一个个按顺序"写"入文件;想读出来,就一个个按顺序从文件中"读"出。

C 标准库已经帮你把读写文件的脏活累活全干了,我们只用记这几个简单函数:

操作函数类比你熟悉的
打开文件FILE *fopen("文件名", "模式");相当于给文件"开门",拿到钥匙FILE*
写文件fprintf(fp, "格式串", 变量...);和printf一模一样,只是写到文件里,不是屏幕
读文件fscanf(fp, "格式串", &变量...);和scanf一模一样,只是从文件里读,不是键盘
关闭文件fclose(fp);用完锁门,必须写

其实你已经会用了,这个和printf/scanf很像,只是换了个前缀,加了个文件指针而已。


2 加入新模块:fileio

开始动手改。新建一个文件夹放文件读写的代码:

code/4-5-学生成绩管理系统-存档版/ ├── model/ ├── view/ ├── controller/ ├── util/ ├── fileio/ ← 新建,专门管存盘读盘 └── main.c

3.1 写头文件:声明两个函数

新建fileio/fileio.h:

#ifndefFILEIO_H#defineFILEIO_H#include"../model/student.h"/* 把整个系统存到文件 students.dat */voidsave_system(StudentSystem*sys,constchar*filename);/* 从文件 students.dat 加载整个系统,成功返回 1,失败返回 0 */intload_system(StudentSystem*sys,constchar*filename);#endif

我们这里还是固定容量数组(10 人)——容量墙留到下一节收拾,现在先把存盘搞定。

3.2 实现:打开 → 写 → 关闭

新建fileio/fileio.c:

#include<stdio.h>#include"../model/student.h"#include"fileio.h"voidsave_system(StudentSystem*sys,constchar*filename){/* 1. 开门:"w" = 写模式,会覆盖原有内容 */FILE*fp=fopen(filename,"w");if(!fp){/* 打开失败(比如目录不存在)直接返回,这句话是必须的,磁盘满了、权限不对都会打不开,打不开就不能往里写了 */return;}/* 2. 先写学生总数,再一个学生一个学生写 */fprintf(fp,"%d\n",sys->count);for(inti=0;i<sys->count;i++){Student*s=&sys->students[i];/* 每个学生占一行:id 姓名 语数英 → 空格分隔,换行结尾 */fprintf(fp,"%d %s %.1f %.1f %.1f\n",s->id,s->name,s->scores[0],s->scores[1],s->scores[2]);}/* 3. 锁门 */fclose(fp);}

我们这里"先写 count,再每行一个学生",读的时候也得按这个顺序来。

3.3 读文件:打开 → 读 → 关闭

还是同一个顺序,只是模式改成"r"(读),用fscanf代替fprintf:

intload_system(StudentSystem*sys,constchar*filename){FILE*fp=fopen(filename,"r");if(!fp){/* 文件不存在(第一次启动)直接返回失败 */return0;}/* 先读学生总数 */fscanf(fp,"%d",&sys->count);/* 再一个个读学生 */for(inti=0;i<sys->count;i++){Student*s=&sys->students[i];fscanf(fp,"%d %s %f %f %f",&s->id,s->name,&s->scores[0],&s->scores[1],&s->scores[2]);/* 读到了,重新算总分平均分 */calculate_student_total(s);}fclose(fp);return1;/* 成功 */}

读到 id、name、三个成绩,和我们写的时候格式完全一致。注意name直接写s->name,不用加&——因为数组名本身就是地址。

读完每个学生,我们调用calculate_student_total重新算总分平均分——总分平均分是算出来的,不用存,磁盘只存原始数据就行。


4 修改 main.c:启动加载,退出保存

就改三处:

#include"fileio/fileio.h"// 1.加入头文件引用intmain(void){StudentSystem sys={0};intchoice;/* 2.程序启动第一件事:从文件读进来 */if(load_system(&sys,"students.dat")){printf("已加载上次保存的 %d 名学生。\n",sys.count);}while(1){/* ... 菜单循环不变 ... */switch(choice){/* ... 各个 case 不变 ... */case7:/* 3.退出前:存盘 */save_system(&sys,"students.dat");printf("数据已保存,感谢使用,再见!\n");return0;default:printf("× 无效输入,请输入 1-7!\n");}}}

完整 main.c:

/* main.c —— 指挥:启动加载,退出保存 */#include<stdio.h>#include"model/student.h"#include"view/view.h"#include"util/input.h"#include"controller/controller.h"#include"fileio/fileio.h"intmain(void){StudentSystem sys={0};intchoice;/* 程序启动第一件事:从文件读进来 */if(load_system(&sys,"students.dat")){printf("\n 已加载上次保存的 %d 名学生。\n",sys.count);}while(1){printf("\n========== 学生成绩管理系统 ==========\n");printf(" 1. 查看所有学生\n");printf(" 2. 添加学生\n");printf(" 3. 删除学生\n");printf(" 4. 修改成绩\n");printf(" 5. 查询学生\n");printf(" 6. 统计信息\n");printf(" 7. 退出\n");printf("请选择操作(1-7):");choice=read_int(1,7);switch(choice){case1:view_show_all(&sys);break;case2:flow_add_student(&sys);break;case3:flow_delete_student(&sys);break;case4:flow_update_score(&sys);break;case5:flow_find_student(&sys);break;case6:view_show_statistics(&sys);break;case7:save_system(&sys,"students.dat");printf("数据已保存,感谢使用,再见!\n");return0;default:printf("× 无效输入,请输入 1-7!\n");}}}

5 运行验收

编译,命令多了一个fileio/fileio.c:

gcc main.c model/student.c view/view.c controller/controller.c util/input.c fileio/fileio.c -o student_mgr.exe ./student_mgr.exe

开始运行验收,跟我走一遍流程:

1.启动,添加 3 名学生,查看一下都在,退出,程序说数据已保存。

2.再启动,程序说"已加载上次保存的 3 名学生",查看一下,果然还在。

恭喜你!现在你的系统不会丢数据了。

去程序目录下找students.dat,用文本编辑器打开,它长这样:

3 1001 张三 85.0 92.0 78.0 1002 李四 76.0 88.0 91.0 1003 王五 90.0 82.5 77.5

一目了然,就是我们之前设置的格式:第一行总数,之后一行一个学生,空格分开。

按ctrl+c退出程序(如果你还开着的话),将students.dat手动修改成这样:

4 1001 张三 85.0 92.0 78.0 1002 李四 76.0 88.0 91.0 1003 王五 90.0 82.5 77.5 1004 赵六 12.0 14.0 2.0

打开,查看:


7 完善代码,防止数组溢出

现在我们容量写死 10。加载时如果文件里学生超过 10 人,你觉得程序会怎么样?

现在我们代码是这么读的:

fscanf(fp,"%d",&sys->count);for(inti=0;i<sys->count;i++){/* ... 读进 sys->students[i] ... */}

如果文件里count是 15,sys->students只有 10 个位置——循环会读到第 11 个,写到数组外面去——这就是数组越界,结果 undefined(程序可能崩、可能乱码、可能什么事都没有但数据错了)。

现在先加个简单防护:

intload_system(StudentSystem*sys,constchar*filename){FILE*fp=fopen(filename,"r");if(!fp)return0;intcount;fscanf(fp,"%d",&count);if(count>MAX_CAPACITY){printf("警告:文件中有 %d 个学生,当前版本最多装 %d 个,只装前 %d 个。\n",count,MAX_CAPACITY,MAX_CAPACITY);count=MAX_CAPACITY;}sys->count=count;for(inti=0;i<sys->count;i++){/* ... 读 ... */}/* ... */}

加入后运行情况:

这样至少不崩,用户也知道怎么回事。下一节我们解决字符串的问题;下下节,我们再学会怎么让数组自己长大,到时候我们将彻底解决数组溢出的问题。


8 总结

  1. 文件 = 磁盘上的一串二进制数,程序关闭后,内存清空,磁盘不清。所以我们存成文件的形式,数据就不会丢掉。
  2. 四个函数:fopen打开 →fprintf/fscanf读写 →fclose关闭。格式要读写一致,这样就不会出错。
  3. 架构自然的拓展:新加一个fileio/功能,model/view/controller 一个都不用改,这就是分层的好处。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 2:09:52

第 9 章 审批与安全机制

&#x1f4cc; 本章目标&#xff1a;实现审批流程和安全机制&#xff0c;让 Agent 在安全约束下工作 ⭐⭐ 难度&#xff1a;基础 | ⏱️ 预计时间&#xff1a;1 小时9.1 为什么需要审批&#xff1f; 先想一个问题 如果你的 AI 助手可以直接执行命令、修改文件、访问数据库&…

作者头像 李华
网站建设 2026/10/20 2:09:06

2027计算机专业毕设怎么选?教你用AI分析20个计算机视觉项目方向(附完整Prompt)

摘要 AI编程时代,计算机专业学生完成毕业设计,最大的困难已经不只是“不会写代码”。 很多同学真正遇到的问题是: 不知道选择什么毕业设计方向; 不知道自己的基础适合什么项目; 不知道如何规划技术路线; 不知道如何把一个深度学习模型变成完整的软件系统; 遇到代码错误…

作者头像 李华
网站建设 2026/10/20 2:08:51

Ionic Toggle 开关组件完全指南:从基础用法到实战案例

1. 引言Ionic 框架作为移动端跨平台开发的利器&#xff0c;提供了丰富且易用的 UI 组件库。其中&#xff0c;Toggle&#xff08;开关&#xff09;组件是移动端表单交互中不可或缺的元素&#xff0c;常用于设置项、权限控制、功能开关等场景。本文将深入讲解 Ionic Toggle 组件的…

作者头像 李华
网站建设 2026/10/20 2:07:55

STM32+华为云IoT+Qt实战:从零打通智能家居数据链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 2:07:31

主客观观测和层级感知嵌套序言| 赵杰 | 量子感知论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 2:06:24

告别单会话限制:RDPWrap 五分钟实现免费多用户并发远程桌面

告别单会话限制&#xff1a;RDPWrap 五分钟实现免费多用户并发远程桌面 【免费下载链接】rdpwrap.ini RDPWrap.ini for RDP Wrapper Library by StasM 项目地址: https://gitcode.com/GitHub_Trending/rd/rdpwrap.ini 家里的电脑跑着重要任务&#xff0c;想远程接手时却…

作者头像 李华