news 2026/9/1 4:59:26

C语言基本文件操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言基本文件操作

文章目录

    • 引入
    • 1. 文件的打开与关闭:先取得通道,再释放资源
      • 1.1 `fopen`:用路径和模式打开文件
      • 1.2 `fclose`:关闭流并处理最后的写入
    • 2. 顺序读写:位置指针自动向前移动
      • 2.1 `fgetc`:从流中读取一个字符
      • 2.2 `fputc`:向流中写入一个字符
      • 2.3 `fgets`:读取一行(最多 `n - 1` 个字符)
      • 2.4 `fputs`:写入字符串,不自动补换行
      • 2.5 `fprintf`:按格式向文件写入文本
      • 2.6 `fscanf`:按格式从文件读取文本
      • 2.7 `fread`:按块读取二进制数据
      • 2.8 `fwrite`:按块写入二进制数据
    • 3. 随机读写:跳到指定位置再处理
      • 3.1 `fseek`:相对某个基准移动位置
      • 3.2 `ftell`:获取当前位置
      • 3.3 `rewind`:回到文件开头
    • 4. 文件读取结束的判断:`EOF`、`feof` 和 `ferror`
      • 4.1 `feof`:查询 EOF 标志
      • 4.2 `ferror` 与 `clearerr`:区分和清除错误状态
    • 5. 缓冲区:为什么写入后文件大小可能暂时不变
      • 5.1 `fflush`:刷新输出流
    • 6. 文本还是二进制?先根据数据形态做选择
    • 7. 小练习
      • 7.1 统计文件中的行数
      • 7.2 文本文件拷贝
    • 总结

引入

内存中的int score = 95;只在程序运行时存在。想让下次启动程序还能得到 95,就需要把字节写入磁盘文件。C 标准库用一组stdio.h函数把磁盘细节封装起来:程序拿到一个FILE *,通过它读写文件。

FILE是库内部维护的结构体,里面记录文件当前位置、读写模式、缓冲区和错误状态。我们只能通过指针使用它,不能直接访问其成员。stdinstdoutstderr也是预先打开的FILE *,所以printf本质上是向标准输出流写入。

1. 文件的打开与关闭:先取得通道,再释放资源


1.1fopen:用路径和模式打开文件

函数原型:

FILE*fopen(constchar*filename,constchar*mode);

成功时返回一个FILE *;失败时返回NULLmode决定“是否允许读写”和“文件不存在时怎么办”。最常用的模式如下:

模式含义文件不存在原有内容
"r"只读失败保留
"w"只写创建清空
"a"追加写创建保留,写入总在末尾
"r+"读写失败保留
"w+"读写创建清空
"a+"读写追加创建保留,写入在末尾

在这些模式后加"b"(如"rb""wb")表示二进制流。

代码示例:

#include<stdio.h>intmain(){FILE*fp=fopen("note.txt","w");if(fp==NULL){perror("fopen");// 输出系统给出的原因return1;}fputs("first line\n",fp);if(fclose(fp)!=0){perror("fclose");return1;}return0;}

执行后,当前工作目录出现note.txt,内容为一行first linefputs的细节稍后介绍。注意"w"会清空已有文件;想保留内容并在尾部增加,应改为"a"

1.2fclose:关闭流并处理最后的写入

intfclose(FILE*stream);

返回0表示成功,非0表示关闭时出错。关闭输出流不仅是“释放指针”,还会把缓冲区中尚未写到操作系统的数据刷新出去。因此即使前面可能出现的fprintf没报错,fclose仍然可能因为磁盘满而失败。

关闭后不能再使用fpfgetc(fp)fclose(fp)都属于未定义行为。若变量还要保留,可写fp = NULL,避免误用。

2. 顺序读写:位置指针自动向前移动

顺序读写适合从文件开头一路处理到结尾。每成功读写一个字符、一个文本行或一块数据,流的位置都会更新。

2.1fgetc:从流中读取一个字符

intfgetc(FILE*stream);

返回值看起来是int而不是char,因为它既要容纳所有字符值,还要额外表示EOF。正确循环必须先接收返回值,再判断:

#include<stdio.h>intmain(){FILE*fp=fopen("note.txt","r");intch;if(fp==NULL)return1;while((ch=fgetc(fp))!=EOF){putchar(ch);}fclose(fp);return0;}

如果note.txt是前面的first line,屏幕会输出first line。不要写成char ch; while ((ch = fgetc(fp)) != EOF):当真实字符值与EOF比较时,可能产生误判。

2.2fputc:向流中写入一个字符

intfputc(intc,FILE*stream);

成功返回写入的字符(转换为unsigned char后再返回),失败返回EOF

代码示例:

#include<stdio.h>intmain(){FILE*fp=fopen("letters.txt","w");if(fp==NULL)return1;for(intch='A';ch<='C';ch++){if(fputc(ch,fp)==EOF){perror("fputc");fclose(fp);return1;}}fclose(fp);return0;}

letters.txt内容为ABC。参数类型是int,但写入的是一个字符;不要把它误解为一次写入四字节整数。

2.3fgets:读取一行(最多n - 1个字符)

char*fgets(char*str,intn,FILE*stream);

它最多读取n - 1个字符,并自动在末尾补\0。如果在达到上限前遇到换行,换行也会被放入数组。成功返回str,遇到文件结束或错误返回NULL

代码示例:

#include<stdio.h>intmain(){charline[32];FILE*fp=fopen("note.txt","r");if(fp==NULL)return1;while(fgets(line,sizeof(line),fp)!=NULL){printf("读到:%s",line);// line 可能已经含换行}fclose(fp);return0;}

如果一行超过 31 个字符,fgets会分多次返回;这不是丢数据,而是需要你继续读取。

2.4fputs:写入字符串,不自动补换行

intfputs(constchar*str,FILE*stream);

成功时返回非负值,失败返回EOF。它会写入字符串中的字符直到\0,但不会自动增加换行:

#include<stdio.h>intmain(){FILE*fp=fopen("message.txt","w");if(fp==NULL)return1;fputs("one line\n",fp);fputs("second line",fp);fclose(fp);return0;}

文件有两行,因为第一段字符串自己包含\n。与puts相比,fputs允许你指定目标文件流。

2.5fprintf:按格式向文件写入文本

intfprintf(FILE*stream,constchar*format,...);

用法与printf类似,只是第一个参数是目标流。返回成功写入的字符数,编码或 I/O 失败时返回负值。

代码示例:

#include<stdio.h>intmain(){FILE*fp=fopen("student.txt","w");if(fp==NULL)return1;intid=1001;doublescore=92.5;if(fprintf(fp,"id=%d score=%.1f\n",id,score)<0){perror("fprintf");fclose(fp);return1;}fclose(fp);return0;}


文件内容是id=1001 score=92.5。注意,和printf一样,格式说明符必须与参数类型匹配;例如float使用%f,整数不要用%s

2.6fscanf:按格式从文件读取文本

intfscanf(FILE*stream,constchar*format,...);

返回成功赋值的字段数;遇到文件结束返回EOF。因此循环应检查“转换数量”,而不是只检查feof

#include<stdio.h>intmain(){FILE*fp=fopen("student.txt","r");intid;doublescore;if(fp==NULL)return1;if(fscanf(fp,"id=%d score=%lf",&id,&score)==2){printf("id=%d, score=%.1f\n",id,score);}else{puts("文件格式不符合预期");}fclose(fp);return0;}


这里&id&score是写入目标地址;double必须使用%lf。格式中的空白可以匹配任意空白,普通字符(如id=)必须与文件一致。对于不可信文本,fgetssscanf往往更容易控制每行的边界。

2.7fread:按块读取二进制数据

size_tfread(void*ptr,size_tsize,size_tcount,FILE*stream);

其中ptr是 指向一块内存缓冲区的指针,该内存至少要有size × count字节大小 ;size是 要读取的单个元素的字节大小;count是要读取的元素个数;每个元素占size字节;返回的是“实际读取的元素个数”,不是字节数。下面一次读取 4 个int

#include<stdio.h>intmain(){inta[4];FILE*fp=fopen("numbers.bin","rb");if(fp==NULL)return1;size_tgot=fread(a,sizeof(a[0]),4,fp);if(got<4&&ferror(fp)){perror("fread");}for(inti=0;i<got;++i){printf("%d ",a[i]);}printf("\n");fclose(fp);return0;}

若文件只剩 2 个整数,got就是 2。fread(a, 1, sizeof(a), fp)则把“元素”定义为字节,返回值才是读取的字节数。直接把结构体写入文件时还会受到字节序、类型大小和内存对齐影响.

2.8fwrite:按块写入二进制数据

size_tfwrite(constvoid*ptr,size_tsize,size_tcount,FILE*stream);

其中ptr是 指向一块内存缓冲区的指针,该内存至少要有size × count字节大小;size是 要读取的单个元素的字节大小;count是要读取的元素个数;每个元素占size字节;返回实际写入的元素个数。
代码示例:

//把数组原样写成二进制#include<stdio.h>intmain(){constinta[]={10,20,30,40};FILE*fp=fopen("numbers.bin","wb");if(fp==NULL)return1;size_twritten=fwrite(a,sizeof(a[0]),4,fp);if(written!=4){perror("fwrite");fclose(fp);return1;}fclose(fp);return0;}

3. 随机读写:跳到指定位置再处理


顺序读取不适合“只改文件中间一条记录”。随机访问通过一个位置指针直接跳转。

3.1fseek:相对某个基准移动位置

intfseek(FILE*stream,longoffset,intorigin);

origin可取SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾)。成功返回 0,失败返回非 0,并清除 EOF 标志。

#include<stdio.h>intmain(){FILE*fp=fopen("letters.txt","rb");//和上文同样内容 letter.txt: ABCintch;if(fp==NULL)return1;if(fseek(fp,1L,SEEK_SET)!=0){fclose(fp);return1;}ch=fgetc(fp);// 读取 'B'printf("字符:%c\n",ch);fclose(fp);return0;}

对二进制流,偏移量通常就是字节数。文本流为了保持可移植性,除 0 和ftell返回的位置外,不要随意计算偏移。

3.2ftell:获取当前位置

longftell(FILE*stream);

成功返回当前位置,失败返回-1L。它常用于记录“读到哪里”,之后配合fseek返回:

longpos=ftell(fp);if(pos==-1L){perror("ftell");}if(fseek(fp,pos,SEEK_SET)!=0){perror("fseek");}

3.3rewind:回到文件开头

voidrewind(FILE*stream);

它等价于把位置设为开头,并清除流的 EOF 和错误标志(不报告错误)。

代码示例:

FILE*fp=fopen("note.txt","r");intfirst,again;if(fp!=NULL){first=fgetc(fp);rewind(fp);again=fgetc(fp);printf("%c %c\n",first,again);// 同一个首字符fclose(fp);}

4. 文件读取结束的判断:EOFfeofferror


4.1feof:查询 EOF 标志

intfeof(FILE*stream);

返回非零表示 EOF 标志已设置。关键点是:EOF 是一次读操作的结果,不是文件中预先存放的字符。所以while (!feof(fp))会多执行一次循环。

4.2ferrorclearerr:区分和清除错误状态

intferror(FILE*stream);voidclearerr(FILE*stream);

读失败后,feofferror可能需要区分:前者表示正常读到末尾,后者表示 I/O 错误。clearerr清除这两个标志,使流可以继续尝试后续操作。

FILE*fp=fopen("note.txt","r");if(fp!=NULL){intch;while((ch=fgetc(fp))!=EOF){// 处理字符}if(feof(fp))puts("正常到达文件末尾");if(ferror(fp))perror("读取失败");clearerr(fp);// 清除状态,谨慎使用fclose(fp);}

clearerr不能修复磁盘或权限问题,只是重置状态;真正的错误原因仍需处理。

5. 缓冲区:为什么写入后文件大小可能暂时不变


标准 I/O 常先把数据放入用户态缓冲区,再批量交给操作系统。这样可以减少频繁系统调用,提高性能,但也意味着程序异常终止时,未刷新的内容可能丢失。

5.1fflush:刷新输出流

intfflush(FILE*stream);

对输出流调用会把缓冲数据推送出去,成功返回 0,失败返回EOF。常见场景是日志程序希望立即看到最新内容:

FILE*fp=fopen("app.log","a");if(fp!=NULL){fprintf(fp,"程序仍在运行\n");if(fflush(fp)==EOF)perror("fflush");fclose(fp);}

fflush(stdin)在标准 C 中没有可移植的“清空输入缓存”含义;不要依赖它处理键盘残留字符。输入流应通过循环读取并丢弃,或重新设计输入流程。

6. 文本还是二进制?先根据数据形态做选择

文本文件(fprintf/fscanffgets/fputs)便于人眼查看和调试,适合配置、日志和简单表格;二进制文件(fread/fwrite)体积小、速度快,适合大量定长记录,但可移植性和版本兼容需要额外设计。

7. 小练习

7.1 统计文件中的行数

要求:使用fgets读取note.txt,统计包含内容的行数(每行的字符数小于255)。

思路:每次fgets成功就得到一行;循环结束后用ferror判断是否因错误中断。

参考答案:

intcount_lines(constchar*path){FILE*fp=fopen(path,"r");charbuf[256];intcount=0;if(fp==NULL)return-1;while(fgets(buf,sizeof(buf),fp)!=NULL){count++;}if(ferror(fp))count=-1;fclose(fp);returncount;}

7.2 文本文件拷贝

要求:把numbers.txt文本文件复制到copy.txt,使用文本模式r/w,缓冲区方式读写。

intcopy_text(constchar*src,constchar*dst){FILE*in=fopen(src,"r");FILE*out=fopen(dst,"w");//检测路径是否有效与打开操作是否成功if(in==NULL||out==NULL){if(in)fclose(in);if(out)fclose(out);return-1;}intch;//使用int类型便于判断EOFwhile((ch=fgetc(in))!=EOF){if(fputc(ch,out)==EOF){fclose(in);fclose(out);return-1;}}intread_ok=!ferror(in);//判断读是否发生错误intin_ok=(fclose(in)==0);//判断是否成功关闭intout_ok=(fclose(out)==0);return(read_ok&&in_ok&&out_ok)?0:-1;}

总结


可靠的文件代码通常遵循同一条线:fopen检查NULL→ 读写函数检查返回值 → 用feof/ferror解释失败原因 → 必要时fflush→ 用fclose检查最终落盘结果。先决定文本还是二进制,再选择顺序还是随机访问;当你能说清“当前位置在哪里、缓冲区里还有什么、失败后如何恢复”,文件操作就不再是只能背函数名的黑盒。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 4:58:36

C++ 运算符重载详解:从基础到日期类完整实现

在 C 面向对象编程中&#xff0c;运算符重载是赋予自定义类型与内置类型一致操作体验的核心机制。它让我们能够用 、-、、 等直观的运算符操作对象&#xff0c;极大提升了代码的可读性和自然性。本文将从基础概念出发&#xff0c;深入讲解赋值运算符重载的要点&#xff0c;并通…

作者头像 李华
网站建设 2026/9/1 4:58:25

58同城算法工程师面试复盘:从KMP到粒子群与系统设计

1. 面试流程与考察侧重点58同城算法工程师的面试&#xff0c;和我预想中不太一样。去之前我以为会全程盯着深度学习模型、推荐系统这些高大上的方向问&#xff0c;结果三轮面下来&#xff0c;发现他们考察的维度相当综合。2023年这个时间节点&#xff0c;58的业务线覆盖招聘、房…

作者头像 李华
网站建设 2026/9/1 4:58:07

华为FreeBuds Pro 5雪域白测评:降噪音质续航与佩戴选购指南

选蓝牙耳机时&#xff0c;真正难的不是预算&#xff0c;而是参数相近的时候怎么判断谁更适合自己。以华为 FreeBuds Pro 5 蓝牙耳机为例&#xff0c;电商页面上雪域白这个配色非常抢眼&#xff0c;但“雪域白值得买吗”这个问题&#xff0c;不能只回答“好看”或“不好看”。真…

作者头像 李华
网站建设 2026/9/1 4:57:03

STM32F407与TLC5947:SPI驱动24路高精度PWM LED实战

简介&#xff1a;围绕STM32F407与TLC5947构建多通道PWM输出扩展方案&#xff0c;面向嵌入式开发者解决MCU原生PWM输出通道不足的问题。方案基于SPI接口驱动16通道12位分辨率的TLC5947&#xff0c;详细说明GPIO复用为SPI主模式、时钟频率设定、PWM占空比寄存器写入、片选与时钟时…

作者头像 李华
网站建设 2026/9/1 4:54:50

河网shp数据处理全攻略:从解压到转换的实战指南

简介&#xff1a;全国河网shp矢量数据集&#xff0c;涵盖一级至五级河流及线状、面状要素&#xff0c;面向GIS学习者、水利规划人员与环境科学研究者&#xff0c;可用于河流分布查询、流域分析及洪水风险预判等场景。压缩包共42个文件&#xff0c;以shp主文件、dbf属性表、prj投…

作者头像 李华