尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

24. 【C语言】把数据存下来:文件操作基础

24. 【C语言】把数据存下来:文件操作基础
📅 发布时间:2026/7/4 4:42:43

前面二十三篇文章,我们写的所有程序都有一个共同特征:数据只在运行时存在。变量在内存里,程序一退出,一切烟消云散。

但真正的软件不是这样的。游戏要保存存档,编辑器要读写文档,数据库要把数据永久保存到磁盘。这就需要一个程序与外部存储世界沟通的桥梁——文件操作。

C 语言通过标准库提供了一套简洁的文件操作函数,核心思路是:把文件看作一个字节流,打开它、读/写它、关闭它。今天我们就来掌握这套“磁盘功夫”。


一、文件指针FILE *:操作文件的“手柄”

在 C 语言里,操作文件不是靠文件名,而是靠一个文件指针——FILE *。你打开一个文件,操作系统会返回一个不透明的指针,之后所有的读写操作都通过这个指针进行。

可以把FILE *理解为“遥控器”:你用它来操控对应的文件,不需要知道内部细节。

#include<stdio.h>intmain(void){FILE*fp;// 声明文件指针fp=fopen("test.txt","w");// 打开文件// ... 使用 fp ...fclose(fp);// 关闭文件return0;}

二、打开文件:fopen

FILE*fopen(constchar*filename,constchar*mode);
  • filename:文件路径(字符串)。
  • mode:打开模式(字符串),决定读还是写、是否清空、是否追加等。
  • 返回值:成功返回FILE*,失败返回NULL。

常用模式一览

模式含义文件不存在时文件存在时
"r"只读返回 NULL从头读
"w"只写创建新文件清空内容,从头写
"a"追加写创建新文件从末尾追加,不清空
"r+"读写返回 NULL从头读写
"w+"读写创建新文件清空内容
"a+"读+追加创建新文件从末尾追加

永远检查fopen的返回值:如果文件打不开(比如路径不存在、权限不够、磁盘满),fopen返回NULL。不检查就直接用,会导致空指针解引用,程序崩溃。

FILE*fp=fopen("data.txt","r");if(fp==NULL){perror("打开文件失败");// perror 打印系统错误信息return1;}

perror是个很方便的函数,它会根据全局错误码errno打印出具体的错误原因(比如 “No such file or directory”)。


三、关闭文件:fclose

intfclose(FILE*fp);
  • 将缓冲区中尚未写入磁盘的数据刷新到文件。
  • 释放系统资源(文件描述符)。
  • 返回 0 表示成功,EOF(通常是 -1)表示失败。

省略fclose在程序正常退出时,操作系统通常会帮你关掉。但养成手动关闭的习惯非常必要——尤其是写操作,不关可能导致数据丢失;长期运行的程序不关会耗尽文件描述符。


四、写文件:fprintf和fputs

1.fprintf—— 格式化写入

fprintf和printf几乎一模一样,只是多了一个FILE*参数指明写入到哪里。

fprintf(fp,"格式字符串",参数...);

示例:把学生信息写入文件

#include<stdio.h>intmain(void){FILE*fp=fopen("students.txt","w");if(fp==NULL){perror("打开文件失败");return1;}fprintf(fp,"%-20s %-10s %-6s\n","姓名","学号","成绩");fprintf(fp,"%-20s %-10d %-6.1f\n","Alice",1001,92.5);fprintf(fp,"%-20s %-10d %-6.1f\n","Bob",1002,85.0);fprintf(fp,"%-20s %-10d %-6.1f\n","Carol",1003,78.5);fclose(fp);printf("数据已写入 students.txt\n");return0;}

运行后打开students.txt,你会看到格式整齐的表格。%-20s表示左对齐占 20 列,和终端输出完全一样。

2.fputs—— 写入整个字符串

fputs("一行文字\n",fp);

比fprintf(fp, "%s", str)更简洁,但不带格式化功能,也不会自动加换行(你需要手动\n)。


五、读文件:fscanf和fgets

1.fscanf—— 格式化读取

和scanf类似,从文件指针读取而非键盘:

fscanf(fp,"格式字符串",地址...);

读回刚才保存的学生数据:

#include<stdio.h>intmain(void){FILE*fp=fopen("students.txt","r");if(fp==NULL){perror("打开文件失败");return1;}charname[50];intid;floatscore;// 跳过标题行charheader[100];fgets(header,sizeof(header),fp);printf("读取的学生数据:\n");while(fscanf(fp,"%s %d %f",name,&id,&score)==3){printf("姓名: %s, 学号: %d, 成绩: %.1f\n",name,id,score);}fclose(fp);return0;}

注意:fscanf遇到空格、换行会停止读字符串。如果name包含空格,会被截断——这时用fgets逐行读更可靠。

判断读取是否结束:fscanf的返回值是成功匹配并赋值的项数。读到文件末尾返回EOF(通常是 -1),匹配失败返回小于期望值的数。

2.fgets—— 安全地读一行

char*fgets(char*buffer,intsize,FILE*fp);
  • buffer:存放读取内容的字符数组。
  • size:最多读取size-1个字符(留一位给'\0')。
  • 读到换行符会保留它,读到文件末尾返回NULL。
charline[256];while(fgets(line,sizeof(line),fp)!=NULL){printf("%s",line);// line 里已包含换行符}

fgets不会溢出,是读取文本文件的推荐方式。


六、文本文件 vs 二进制文件

上面的例子都是文本文件——数据以人类可读的字符形式存储,数字 92.5 被写成 ‘9’ ‘2’ ‘.’ ‘5’ 四个字符。文本文件优点是直接用编辑器打开查看,缺点是有转换开销、精度可能丢失、文件较大。

二进制文件则把内存中的位模式原样写入磁盘。int就写 4 字节,double写 8 字节。优点:更紧凑、读写更快、精度不丢失。缺点:用编辑器打开是乱码。

我们现在先只关注文本文件(下一篇文章会系统讲fread/fwrite的二进制操作)。现在你只需知道,C 把文件都看作字节流,文本和二进制只是解释方式不同。


七、完整实战:学生成绩文件的读写

把结构体(第二十一篇)和文件操作结合起来,做一个简单的成绩单保存/读取工具。

#include<stdio.h>#include<stdlib.h>#include<string.h>#defineMAX_NAME50#defineFILENAME"grades.txt"typedefstruct{charname[MAX_NAME];floatscore;}Student;voidsave_students(Student*students,intcount){FILE*fp=fopen(FILENAME,"w");if(fp==NULL){perror("保存失败");return;}fprintf(fp,"%d\n",count);// 第一行记录学生数量for(inti=0;i<count;i++){fprintf(fp,"%s %.1f\n",students[i].name,students[i].score);}fclose(fp);printf("已保存 %d 条记录到 %s\n",count,FILENAME);}intload_students(Student*students,intmax_count){FILE*fp=fopen(FILENAME,"r");if(fp==NULL){perror("加载失败");return0;}intcount;fscanf(fp,"%d",&count);if(count>max_count)count=max_count;for(inti=0;i<count;i++){fscanf(fp,"%s %f",students[i].name,&students[i].score);}fclose(fp);returncount;}intmain(void){Student students[100];intcount=0;intchoice;while(1){printf("\n1.添加学生 2.显示全部 3.保存 4.加载 0.退出\n");printf("选择: ");scanf("%d",&choice);if(choice==0)break;elseif(choice==1){if(count>=100){printf("已满\n");continue;}printf("姓名 成绩: ");scanf("%s %f",students[count].name,&students[count].score);count++;}elseif(choice==2){printf("当前 %d 条记录:\n",count);for(inti=0;i<count;i++){printf(" %s: %.1f\n",students[i].name,students[i].score);}}elseif(choice==3){save_students(students,count);}elseif(choice==4){count=load_students(students,100);printf("已加载 %d 条记录\n",count);}}return0;}

这个程序综合了结构体、数组、循环、分支、文件读写。运行它,添加几个学生,保存后退出;再重新运行,加载文件,数据就恢复了——你第一次实现了真正的“持久化”。


八、常见错误与陷阱

1. 忘记检查fopen的返回值

FILE*fp=fopen("missing.txt","r");fprintf(fp,"hello");// fp 是 NULL,崩溃

任何文件操作前先判空。

2. 用"w"打开已有文件,内容被清空

FILE*fp=fopen("important.txt","w");// 旧内容瞬间消失!

如果只是想添加,用"a"(追加)模式。

3. 读写后忘记fclose

尤其是在写操作后,不关可能导致缓冲区里的数据还没写到磁盘,造成文件内容不完整。

4. 用fscanf读字符串不限制宽度

charname[20];fscanf(fp,"%s",name);// 文件里若有一行超长,就溢出了

应该用%19s限制宽度,或者用fgets。

5. 多次调用fgets/fscanf时没考虑上一行的换行符残留

fscanf(fp,"%d",&n);// 读完数字,换行符还在fgets(line,100,fp);// 立即读完那个换行符,得到空行

混合使用fscanf和fgets时,可以在fscanf后用getchar()或fgetc(fp)吃掉换行符。或者统一用fgets+sscanf解析。


九、小结

今天你让程序有了“记忆力”。核心知识:

  • FILE *是操作文件的手柄,用fopen获取,用fclose归还。
  • "r"、"w"、"a"等模式控制了读、写、追加行为。
  • fprintf/fputs写文件,fscanf/fgets读文件。
  • fgets是安全读行的首选,混合输入时要小心换行符残留。
  • 结构体 + 文件操作 = 简单的数据持久化系统。

文本文件足以应对配置、日志、简单数据存储的需求。但如果你想高效存储大量数值、实现随机读写、或者构建一个简易数据库文件,就需要二进制文件与随机读写。下一篇,我们就来学习fread、fwrite、fseek、ftell——打开文件操作的另一扇大门。


课后小练习

  1. 编写一个程序,把 1 到 100 的整数逐行写入numbers.txt,每行一个数。
  2. 读取上一题生成的numbers.txt,计算所有整数的总和并打印。
  3. 实现一个简单的“日志记录器”:每次运行程序,让用户输入一行文字,以追加模式写入log.txt,并在每行前加上时间戳(可以用time库的time()和ctime()获取当前时间字符串)。
  4. (陷阱题)下面的代码有什么问题?如何修正?
    FILE*fp=fopen("data.txt","w");if(fp=NULL){printf("错误\n");}fprintf(fp,"Hello");fclose(fp);

我们下期见!

相关新闻

  • Codex 实战 Skills:用 Skill 一键为 API 接口生成 100% 覆盖率的 Python pytest 用例
  • 代理系统架构_agent-architecture
  • 层级协调系统_agent-hierarchical-coordinator

最新新闻

  • gh-markdown-preview vs 其他预览工具:为什么GitHub官方风格更胜一筹
  • 如何永久保存微信聊天记录?WeChatMsg让每一段对话都成为珍贵数字记忆
  • httpcache核心组件解析:深入理解Transport和Cache接口
  • Instatic代码质量标准:代码审查与质量 Gates 全面指南
  • ToastNotifications高级功能:键盘事件处理与通知动画效果实现
  • 权限维持攻击的数据痕迹分析与检测实战

日新闻

  • STM32F745VG与MC6470 IMU的高性能姿态控制系统设计
  • 机器不消费,人何以生存
  • AI项目操作手册编写规范与最佳实践

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号