C语言学习笔记20260712-文件操作核心函数详解与实例
在C语言中,程序运行时产生的数据默认存储在内存中,一旦程序退出,内存被系统回收,数据就会永久丢失。文件操作的核心价值在于将数据从内存写入硬盘,实现数据的“持久化存储”。C语言标准库(<stdio.h>)提供了一套强大的文件操作接口,下面将为你详细梳理核心函数、读写方式及易错点。
一、 文件的打开与关闭
文件操作的第一步是建立程序与硬盘文件的连接,使用完毕后必须断开连接。
1. fopen:打开文件
- 原型:
FILE *fopen(const char *filename, const char *mode); - 功能:打开指定路径的文件。
FILE是一个结构体指针,包含了文件缓冲区、当前读写位置等信息,是操作文件的“身份证”。 - 返回值:成功返回指向该文件的指针;失败返回
NULL。 - 常用打开模式:
"r":只读打开文本文件(文件必须存在)。"w":只写打开文本文件(若存在则清空内容,不存在则新建)。"a":追加打开文本文件(写入的数据会附加在文件末尾)。"rb"/"wb":以二进制模式只读/只写打开(适用于非字符数据,如图片、音频、结构体)。"r+"/"w+"/"a+":以读写模式打开。
2. fclose:关闭文件
- 原型:
int fclose(FILE *stream); - 功能:关闭文件并刷新缓冲区,确保数据真正写入硬盘。
- 返回值:成功返回 0,失败返回 EOF。
#include<stdio.h>#include<stdlib.h>intmain(){// 以只写模式打开(或新建)一个文本文件FILE*fp=fopen("test.txt","w");// 必须检查文件是否打开成功if(fp==NULL){perror("fopen failed");return1;}printf("文件打开成功,准备写入数据...\n");// 文件操作...// 使用完毕后必须关闭文件if(fclose(fp)!=0){perror("fclose failed");return1;}fp=NULL;// 防止悬空指针return0;}二、 文件的顺序读写
顺序读写是指从文件开头按顺序一笔一笔地读取或写入数据,这是最常用的文件操作方式。
1. 字符读写:fgetc / fputc
适用于逐个字符处理文本,如统计字符数、过滤特定字符。
fgetc:从流中读取一个字符,成功返回字符的ASCII码,失败或读到文件尾返回EOF。fputc:向流中写入一个字符,成功返回该字符,失败返回EOF。
// 示例:将用户输入的字符写入文件,直到输入 '$' 为止voidchar_io_example(){FILE*fp=fopen("char_test.txt","w");if(fp==NULL)return;charch;printf("请输入字符(输入$结束):");while((ch=getchar())!='$'){fputc(ch,fp);// 写入字符到文件}fclose(fp);}2. 文本行读写:fgets / fputs
适用于按行处理文本,如读取配置文件、日志文件。
fgets:从流中读取一行(或指定大小)的字符串到缓冲区。遇到换行符\n或读取满n-1个字符时停止,并自动添加\0。成功返回缓冲区地址,失败或读到文件尾返回NULL。fputs:向流中写入一个字符串(不会自动添加换行符)。
// 示例:向文件写入一行文本,并重新读取出来voidstring_io_example(){FILE*fp=fopen("line_test.txt","w+");// 读写模式if(fp==NULL)return;constchar*str="Hello, C Language File!\n";fputs(str,fp);// 写入字符串rewind(fp);// 将文件指针重置到开头,准备读取charbuffer={0};if(fgets(buffer,sizeof(buffer),fp)!=NULL){printf("读取到的内容:%s",buffer);}fclose(fp);}3. 格式化读写:fprintf / fscanf
适用于处理包含多种数据类型(如整数、浮点数、字符串)的结构化文本数据。
fprintf:将格式化数据写入文件(类似printf)。fscanf:从文件中按格式读取数据(类似scanf)。
// 示例:保存和读取学生信息structStudent{charname;intage;floatscore;};voidformat_io_example(){structStudentstu={"Alice",20,95.5};FILE*fp=fopen("student.txt","w+");if(fp==NULL)return;// 格式化写入fprintf(fp,"%s %d %.1f",stu.name,stu.age,stu.score);rewind(fp);// 重置指针structStudentnew_stu;// 格式化读取fscanf(fp,"%s %d %f",new_stu.name,&new_stu.age,&new_stu.score);printf("姓名:%s, 年龄:%d, 分数:%.1f\n",new_stu.name,new_stu.age,new_stu.score);fclose(fp);}4. 二进制读写:fread / fwrite
适用于高效存储结构体、数组等非字符数据。二进制文件将内存中的数据原封不动地保存,存取速度快,占用空间小。
fwrite:向文件写入二进制数据。fread:从文件读取二进制数据。
// 示例:将结构体数组以二进制形式存入文件voidbinary_io_example(){structStudentclass_arr={{"Bob",21,88.0},{"Cathy",19,92.5}};// 使用 wb 二进制写模式FILE*fp=fopen("class.bin","wb");if(fp==NULL)return;// 写入:缓冲区首地址, 单个元素大小, 元素个数, 文件指针size_twritten=fwrite(class_arr,sizeof(structStudent),2,fp);printf("成功写入 %zu 条记录\n",written);fclose(fp);// 读取演示fp=fopen("class.bin","rb");structStudentread_arr;// 读取:返回实际成功读取的元素个数size_tread_count=fread(read_arr,sizeof(structStudent),2,fp);for(size_ti=0;i<read_count;i++){printf("二进制读取 - 姓名:%s\n",read_arr[i].name);}fclose(fp);}三、 文件的随机读写
有时我们需要跳过文件中间的部分,直接读取或修改特定位置的数据,这时需要使用定位函数。
fseek:重定位文件指针。fseek(fp, offset, whence)。whence可以是SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾)。ftell:返回当前文件指针相对于文件开头的偏移量(字节数)。rewind:将文件指针重置到文件开头(等价于fseek(fp, 0, SEEK_SET))。
// 示例:修改文件中特定位置的数据voidrandom_access_example(){FILE*fp=fopen("data.txt","w+");if(fp==NULL)return;fputs("Hello World",fp);// 将指针移动到第6个字节处(即 'W' 的位置)fseek(fp,6,SEEK_SET);fputs("C",fp);// 将 'W' 替换为 'C'rewind(fp);// 回到开头charbuf;fgets(buf,sizeof(buf),fp);printf("修改后的内容:%s\n",buf);// 输出: Hello Corldfclose(fp);}四、 核心易错点与避坑指南
1. 文件读取结束的正确判定
致命误区:绝不能直接用feof()的返回值作为循环条件来判断文件是否结束!feof只有在“读取失败后”才能准确判断是否是因为遇到了文件尾。
- 文本文件:循环条件应判断
fgetc() != EOF或fgets() != NULL。循环结束后,再用feof()或ferror()判断是正常读完还是出错。 - 二进制文件:循环条件应判断
fread的返回值是否小于实际要读的个数。
// 文本文件读取的正确姿势intc;while((c=fgetc(fp))!=EOF){putchar(c);}// 循环结束后判断原因if(ferror(fp)){puts("读取过程中发生IO错误");}elseif(feof(fp)){puts("文件正常读取完毕");}2. 文件缓冲区机制
C语言文件操作默认带有缓冲区(全缓冲)。调用fprintf或fwrite时,数据可能只是暂时存放在内存的缓冲区中,并未立刻写入硬盘。
- 刷新缓冲区:调用
fclose()关闭文件时会自动刷新;或者手动调用fflush(fp)强制将缓冲区数据写入硬盘。 - 风险:如果程序异常崩溃(如被
kill -9强杀)而未执行fclose,缓冲区内的数据将会丢失。
3. 标准I/O与系统I/O的区别
- 标准I/O (
fopen/fread等):跨平台(Windows/Linux通用),自带缓冲区,效率高,基于FILE*指针。 - 系统I/O (
open/read/write等):仅限Linux/Unix,无缓冲区(直接系统调用),基于文件描述符(整数int fd),常用于设备文件驱动开发。日常应用开发推荐优先使用标准I/O。