济南网站建设公司熊掌号,沈阳网站建设dnglzx,阿里营销网站建设,网络推广营销方式文件操作
目录
文件操作
1. #x1f31f; 为什么需要文件操作#xff1f;#xff08;内存数据的 “永久存储” 方案#xff09;
2. #x1f4cc; 什么是文件#xff1f;#xff08;程序文件 vs 数据文件#xff09;
2.1 程序文件
2.2 数据文件
文件名#xff0…文件操作目录文件操作1. 为什么需要文件操作内存数据的 “永久存储” 方案2. 什么是文件程序文件 vs 数据文件2.1 程序文件2.2 数据文件文件名文件的 “唯一标识”3. 文本文件 vs 二进制文件数据存储的两种形式3.1 核心定义3.2 直观示例整数10000的存储对比3.3 代码验证文本 vs 二进制存储4. 文件的打开与关闭核心操作fopen/fclose4.1 流与标准流默认打开的 3 个流4.2 文件指针FILE*文件的 “身份证”4.3 文件打开方式详解表格汇总4.4 打开与关闭文件的正确示例易错点标注5. 文件的顺序读写8 个核心函数 对比5.1 字符读写fputc/fgetc单个字符操作fputc字符输出函数写字符fgetc字符输入函数读字符5.2 行读写fputs/fgets整行数据操作fputs文本行输出函数写整行fgets文本行输入函数读整行易错点5.3 格式化读写fprintf/fscanf结构体 / 多类型数据fprintf格式化输出函数写多类型数据fscanf格式化输入函数读多类型数据易错点5.4 二进制读写fwrite/fread高效存储fwrite二进制输出函数写二进制数据fread二进制输入函数读二进制数据5.5 拓展三组格式化函数对比实战示例sscanf/sprintf6. 文件的随机读写灵活定位fseek/ftell/rewind6.1 fseek定位文件指针实战示例定位读取文件内容6.2 ftell获取文件指针偏移量6.3 rewind重置文件指针到开头7. ❗ 文件读取结果判定feof/ferror区分 “文件尾” 和 “读错误”7.1 feof判断是否读到文件尾7.2 ferror判断是否发生读错误实战示例正确判定读取结束原因异常情况测试故意出错8. 文件缓冲区为什么数据没立刻写到硬盘缓冲区的作用实战验证缓冲区存在关键结论9. 实战文件拷贝程序文本 / 二进制通用核心思路代码实现优化增大缓冲区提升效率10. ⚠️ 常见文件操作易错点避坑指南11. ✅ 文件操作最佳实践总结✨引言在 C 语言中程序运行时的数据默认存储在内存中 —— 一旦程序退出内存被系统回收数据就会永久丢失。比如你输入一个数字10程序退出后再次运行上次的10就不见了。如果想让数据持久化保存比如存到硬盘就需要用到「文件操作」。本文将从 “为什么用文件” 到 “缓冲区原理”用通俗比喻 实战代码拆解文件操作的核心知识点包括文件类型、打开关闭、顺序 / 随机读写、错误判定还会补充大量细节和实战案例帮你彻底掌握文件操作1. 为什么需要文件操作内存数据的 “永久存储” 方案先看一个简单的例子#include stdio.h int main() { int n 0; scanf(%d, n); // 输入10 printf(%d, n); // 打印10 return 0; }程序运行时n10存储在内存栈区程序退出后栈区内存被系统回收10消失再次运行程序无法获取上次的10。文件操作的核心价值将数据从内存写入硬盘或从硬盘读入内存实现数据的 “持久化存储”。就像把临时放在桌上的文件内存放进抽屉硬盘永久保存。2. 什么是文件程序文件 vs 数据文件放在硬盘上的数据集统称为 “文件”在 C 语言中分为两类2.1 程序文件用于存放程序代码和编译相关的文件例如源文件.c、目标文件.obj、可执行文件.exe。2.2 数据文件用于存放程序运行时读写的数据不是程序本身例如程序读取的配置文件config.txt、程序输出的日志文件log.txt。文件名文件的 “唯一标识”一个完整的文件名包含三部分文件路径 文件名主干 文件后缀示例C:\code\test.txt路径C:\code\文件所在位置主干test文件名后缀.txt文件类型文本文件。3. 文本文件 vs 二进制文件数据存储的两种形式根据数据的存储方式数据文件分为「文本文件」和「二进制文件」核心区别是是否将内存中的二进制数据转换为 ASCII 码。3.1 核心定义类型存储规则特点文本文件内存中的二进制数据→转换为 ASCII 码→存储到硬盘可被记事本打开人类可读二进制文件内存中的二进制数据→直接存储到硬盘不转换不可被记事本直接读取乱码存储高效3.2 直观示例整数10000的存储对比内存中10000的二进制00000000 00000000 00100111 000100004 字节文本文件存储转换为 5 个 ASCII 码字符1→49、0→48、0→48、0→48、0→48占用 5 字节二进制文件存储直接存储 4 字节二进制数据占用 4 字节。3.3 代码验证文本 vs 二进制存储#include stdio.h int main() { int a 10000; // 1. 二进制存储wb模式 FILE* pf1 fopen(binary.txt, wb); if (pf1 ! NULL) { fwrite(a, 4, 1, pf1); // 直接写二进制数据4字节 fclose(pf1); pf1 NULL; } // 2. 文本存储w模式 FILE* pf2 fopen(text.txt, w); if (pf2 ! NULL) { fprintf(pf2, %d, a); // 转换为ASCII码存储5字节 fclose(pf2); pf2 NULL; } return 0; }用记事本打开binary.txt显示乱码二进制数据用记事本打开text.txt显示10000ASCII 码人类可读。4. 文件的打开与关闭核心操作fopen/fclose文件操作遵循 “三段论”打开文件→读写文件→关闭文件就像 “喝水”打开水瓶→喝水→关闭水瓶。4.1 流与标准流默认打开的 3 个流程序要和外部设备键盘、屏幕、硬盘文件交互需要通过 “流”数据传输的通道。C 语言启动时会默认打开 3 个标准流无需手动打开标准流作用对应设备常用函数stdin标准输入流读取数据键盘scanf、fgetcstdout标准输出流输出数据屏幕printf、fputcstderr标准错误流输出错误信息屏幕perror、fprintf 比喻流就像 “数据线”连接程序和外部设备数据通过 “数据线” 传输。4.2 文件指针FILE*文件的 “身份证”每个被打开的文件系统都会在内存中开辟一块 “文件信息区”存储文件名、状态、当前读写位置等信息。这个信息区被封装在FILE结构体中通过FILE*类型的指针文件指针来访问。// FILE* 指针指向文件信息区间接操作文件 FILE* pf1; // 指向文件1的信息区 FILE* pf2; // 指向文件2的信息区打开文件时fopen返回文件指针指向该文件的信息区关闭文件时fclose释放文件信息区指针需置空避免野指针。4.3 文件打开方式详解表格汇总打开文件的核心函数是fopen第二个参数mode指定打开方式不同方式决定了文件的读写权限和创建规则打开方式类型核心权限文件不存在时适用场景r文本只读输入出错返回 NULL读取已存在的文本文件w文本只写输出创建新文件覆盖 / 创建文本文件并写入a文本追加在文件尾写入创建新文件向文本文件尾添加数据r文本读写先读出错读写已存在的文本文件w文本读写先写创建新文件覆盖 / 创建文本文件并读写a文本读写追加 读创建新文件读写文本文件写在尾部rb二进制只读输入出错读取已存在的二进制文件wb二进制只写输出创建新文件覆盖 / 创建二进制文件并写入ab二进制追加在文件尾写入创建新文件向二进制文件尾添加数据rb二进制读写先读出错读写已存在的二进制文件wb二进制读写先写创建新文件覆盖 / 创建二进制文件并读写ab二进制读写追加 读创建新文件读写二进制文件写在尾部4.4 打开与关闭文件的正确示例#include stdio.h int main() { // 1. 打开文件读文本文件r模式 FILE* pf fopen(test.txt, r); // 关键判断文件是否打开成功避免NULL指针 if (pf NULL) { perror(fopen failed); // 打印错误信息如fopen failed: No such file or directory return 1; // 打开失败退出程序 } // 2. 读写文件后续讲解 // ... // 3. 关闭文件必须否则内存泄漏、数据丢失 fclose(pf); pf NULL; // 指针置空避免野指针 return 0; }易错点标注// ❌ 错误1未判断打开失败 FILE* pf fopen(test.txt, r); fgetc(pf); // 若pf为NULL崩溃 // ❌ 错误2关闭后未置空 fclose(pf); fputc(a, pf); // 野指针非法访问 // ❌ 错误3打开方式与操作不匹配 FILE* pf fopen(test.txt, r); // 只读模式 fputc(a, pf); // 错误只读模式不能写5. 文件的顺序读写8 个核心函数 对比顺序读写是指从文件开头到结尾按顺序读写数据不能跳着来核心是 8 个函数分为 4 组5.1 字符读写fputc/fgetc单个字符操作fputc字符输出函数写字符原型int fputc(int character, FILE* stream);功能将字符character写入stream流文件 / 屏幕返回值成功返回写入的字符ASCII 码失败返回EOF-1。int main() { // 打开文件写文本文件w模式 FILE* pf fopen(test.txt, w); if (pf NULL) { perror(fopen); return 1; } // 写字符单个写入 fputc(a, pf); fputc(b, pf); fputc(c, pf); // 写字符循环写入a~z for (char ch a; ch z; ch) { fputc(ch, pf); // 写入abcdefghijklmnopqrstuvwxyz } fclose(pf); pf NULL; return 0; }结果test.txt中存储abcabcdefghijklmnopqrstuvwxyz前 3 个是单个写入后 26 个是循环写入。fgetc字符输入函数读字符原型int fgetc(FILE* stream);功能从stream流文件 / 键盘读取单个字符返回值成功返回字符的 ASCII 码失败或读到文件尾返回EOF-1。int main() { // 打开文件读文本文件r模式 FILE* pf fopen(test.txt, r); if (pf NULL) { perror(fopen); return 1; } // 读字符循环读取所有字符直到EOF int ch 0; // 用int接收因为EOF是-1char无法存储 while ((ch fgetc(pf)) ! EOF) { printf(%c, ch); // 输出abcabcdefghijklmnopqrstuvwxyz } fclose(pf); pf NULL; return 0; } 关键用int接收fgetc的返回值因为char的范围是-128~127而EOF是-1若字符是0xFFASCII 码 255会和EOF冲突。5.2 行读写fputs/fgets整行数据操作fputs文本行输出函数写整行原型int fputs(const char* str, FILE* stream);功能将字符串str写入stream流不自动添加换行符返回值成功返回非负数失败返回EOF。int main() { FILE* pf fopen(test.txt, w); if (pf NULL) return 1; // 写整行不自动加换行 fputs(hello world, pf); fputs(hello bit, pf); // 结果hello worldhello bit同一行 // 手动加换行符 fputs(hello world\n, pf); fputs(hello bit\n, pf); // 结果hello world行1hello bit行2 fclose(pf); pf NULL; return 0; }fgets文本行输入函数读整行原型char* fgets(char* str, int num, FILE* stream);功能从stream流读取最多num-1个字符留 1 个存\0遇到\n或文件尾停止返回值成功返回str存储字符串的地址失败或文件尾返回NULL。int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; char arr[20] {0}; // 读第一行最多读19个字符num20 fgets(arr, 20, pf); printf(%s, arr); // 输出hello worldhello bit无换行 // 读第二行 fgets(arr, 20, pf); printf(%s, arr); // 输出hello world带换行 // 循环读取所有行 while (fgets(arr, 20, pf) ! NULL) { printf(%s, arr); } fclose(pf); pf NULL; return 0; }易错点fgets的num必须大于字符串长度 1留\0否则会截断字符串fgets会读取\n并存储在字符串中打印时无需额外加\n。5.3 格式化读写fprintf/fscanf结构体 / 多类型数据fprintf格式化输出函数写多类型数据原型int fprintf(FILE* stream, const char* format, ...);功能将多类型数据int/char/struct 等按格式写入stream流对比printfprintf默认写入stdout屏幕fprintf可指定流文件 / 屏幕。// 定义结构体 struct Student { char name[20]; int age; float score; }; int main() { struct Student s {张三, 20, 65.5f}; // 打开文件写文本文件 FILE* pf fopen(student.txt, w); if (pf NULL) return 1; // 写结构体数据到文件 fprintf(pf, %s %d %.1f, s.name, s.age, s.score); // 写屏幕等价于printf fprintf(stdout, %s %d %.1f\n, s.name, s.age, s.score); fclose(pf); pf NULL; return 0; }结果student.txt中存储张三 20 65.5屏幕输出相同内容。fscanf格式化输入函数读多类型数据原型int fscanf(FILE* stream, const char* format, ...);功能从stream流按格式读取多类型数据对比scanfscanf默认读取stdin键盘fscanf可指定流文件 / 键盘。struct Student { char name[20]; int age; float score; }; int main() { struct Student s {0}; // 打开文件读文本文件注意必须用r模式不能用w FILE* pf fopen(student.txt, r); if (pf NULL) return 1; // 从文件读取数据到结构体 fscanf(pf, %s %d %f, s.name, s.age, s.score); // 从键盘读取等价于scanf fscanf(stdin, %s %d %f, s.name, s.age, s.score); // 打印验证 printf(%s %d %.1f\n, s.name, s.age, s.score); fclose(pf); pf NULL; return 0; }易错点fscanf读取字符串时name是数组名本身是地址无需加打开文件时读操作必须用r/r模式写操作必须用w/w/a/a模式否则操作失败。5.4 二进制读写fwrite/fread高效存储二进制读写直接操作内存中的二进制数据不转换为 ASCII 码存储效率高占用空间小、速度快适用于结构体、数组等复杂数据。fwrite二进制输出函数写二进制数据原型size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);参数ptr指向要写入的数据数组 / 结构体地址size每个数据的字节数count数据的个数stream目标流只能是文件流不能是 stdin/stdout返回值成功写入的数据个数。int main() { int arr[] {1, 2, 3, 4, 5}; // 打开文件写二进制文件wb模式 FILE* pf fopen(binary_arr.txt, wb); if (pf NULL) return 1; // 写数组到文件5个int每个4字节 size_t ret fwrite(arr, sizeof(int), 5, pf); printf(成功写入%d个数据\n, ret); // 输出成功写入5个数据 fclose(pf); pf NULL; return 0; }fread二进制输入函数读二进制数据原型size_t fread(void* ptr, size_t size, size_t count, FILE* stream);功能从文件流读取二进制数据到ptr指向的空间返回值成功读取的数据个数小于count表示读取结束或失败。int main() { int arr[5] {0}; // 打开文件读二进制文件rb模式 FILE* pf fopen(binary_arr.txt, rb); if (pf NULL) return 1; // 读文件到数组最多读5个int size_t ret fread(arr, sizeof(int), 5, pf); printf(成功读取%d个数据, ret); for (int i 0; i ret; i) { printf(%d , arr[i]); // 输出1 2 3 4 5 } fclose(pf); pf NULL; return 0; }5.5 拓展三组格式化函数对比函数功能适用场景scanf从标准输入流stdin读取格式化数据键盘输入→程序fscanf从指定输入流文件 / 键盘读取格式化数据文件 / 键盘输入→程序sscanf从字符串中读取格式化数据字符串→程序printf向标准输出流stdout输出格式化数据程序→屏幕输出fprintf向指定输出流文件 / 屏幕输出格式化数据程序→文件 / 屏幕输出sprintf将格式化数据转换为字符串程序→字符串实战示例sscanf/sprintf#include stdio.h struct Student { char name[20]; int age; float score; }; int main() { char buf[100] {0}; struct Student s {张三, 20, 65.5f}; struct Student t {0}; // 1. sprintf将结构体数据转为字符串 sprintf(buf, %s %d %.1f, s.name, s.age, s.score); printf(字符串%s\n, buf); // 输出字符串张三 20 65.5 // 2. sscanf从字符串读取数据到结构体 sscanf(buf, %s %d %f, t.name, t.age, t.score); printf(结构体%s %d %.1f\n, t.name, t.age, t.score); // 输出张三 20 65.5 return 0; }6. 文件的随机读写灵活定位fseek/ftell/rewind顺序读写只能从开头到结尾按顺序操作随机读写可以通过 “定位文件指针”跳转到文件任意位置读写比如直接读第 5 个字符、修改中间数据。6.1 fseek定位文件指针原型int fseek(FILE* stream, long int offset, int origin);功能根据origin起始位置和offset偏移量定位文件指针参数说明origin起始位置3 种选择SEEK_SET文件开头0SEEK_CUR文件指针当前位置SEEK_END文件末尾offset偏移量正数→向后移负数→向前移。实战示例定位读取文件内容假设test.txt中存储abcdefghi9 个字符索引 0~8#include stdio.h int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; int ch 0; // 1. 读第一个字符a ch fgetc(pf); printf(%c\n, ch); // 输出a // 2. 从当前位置a后向后移4个字符→f fseek(pf, 4, SEEK_CUR); ch fgetc(pf); printf(%c\n, ch); // 输出f // 3. 从文件开头向后移5个字符→f fseek(pf, 5, SEEK_SET); ch fgetc(pf); printf(%c\n, ch); // 输出f // 4. 从文件末尾向前移4个字符→f fseek(pf, -4, SEEK_END); ch fgetc(pf); printf(%c\n, ch); // 输出f fclose(pf); pf NULL; return 0; }6.2 ftell获取文件指针偏移量原型long int ftell(FILE* stream);功能返回文件指针相对于文件开头的偏移量字节数。int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; fseek(pf, 0, SEEK_END); // 定位到文件末尾 long int len ftell(pf); // 获取偏移量文件长度 printf(文件长度%ld字节\n, len); // 输出9字节 fclose(pf); pf NULL; return 0; }6.3 rewind重置文件指针到开头原型void rewind(FILE* stream);功能将文件指针重置到文件开头。int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; int ch fgetc(pf); printf(%c\n, ch); // 输出a fseek(pf, -4, SEEK_END); ch fgetc(pf); printf(%c\n, ch); // 输出f rewind(pf); // 重置到开头 ch fgetc(pf); printf(%c\n, ch); // 输出a fclose(pf); pf NULL; return 0; }7. ❗ 文件读取结果判定feof/ferror区分 “文件尾” 和 “读错误”文件读取结束有两种原因正常结束读到文件末尾EOF异常结束读取过程中发生错误如文件损坏、权限不足。仅通过fgetc返回EOF无法区分这两种情况需要用feof和ferror函数判定。7.1 feof判断是否读到文件尾原型int feof(FILE* stream);功能检测文件是否因读到末尾而结束返回值是→非 0 值否→0。7.2 ferror判断是否发生读错误原型int ferror(FILE* stream);功能检测文件是否因错误而结束返回值是→非 0 值否→0。实战示例正确判定读取结束原因#include stdio.h int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; int ch 0; // 循环读取 while ((ch fgetc(pf)) ! EOF) { printf(%c, ch); } printf(\n); // 判定结束原因 if (feof(pf)) { printf(读取正常结束已到文件末尾\n); } else if (ferror(pf)) { perror(读取异常结束); } fclose(pf); pf NULL; return 0; }异常情况测试故意出错int main() { FILE* pf fopen(test.txt, r); if (pf NULL) return 1; // 错误操作只读模式下写数据 char ch x; for (ch a; ch z; ch) { fputc(ch, pf); // 只读模式不允许写会触发错误 } // 判定错误 if (ferror(pf)) { perror(操作失败); // 输出操作失败: Bad file descriptor } fclose(pf); pf NULL; return 0; }8. 文件缓冲区为什么数据没立刻写到硬盘C 语言文件操作存在 “缓冲区” 机制 —— 数据不会直接写入硬盘而是先存入内存中的缓冲区当缓冲区满、调用fflush或fclose时才会将数据同步到硬盘。缓冲区的作用减少硬盘 I/O 次数硬盘读写速度慢缓冲区批量写入更高效比喻缓冲区就像 “快递驿站”快递员不会收到一个快递就送一次而是攒一批再送提高效率。实战验证缓冲区存在#include stdio.h #include windows.h // Sleep函数头文件Windows // #include unistd.h // sleep函数头文件Linux int main() { FILE* pf fopen(buffer.txt, w); if (pf NULL) return 1; fputs(abcdef, pf); // 数据写入缓冲区未同步到硬盘 printf(睡眠10秒此时打开buffer.txt无内容\n); Sleep(10000); // 睡眠10秒WindowsLinux用sleep(10) fflush(pf); // 手动刷新缓冲区数据同步到硬盘 printf(刷新缓冲区后睡眠10秒此时打开buffer.txt有内容\n); Sleep(10000); fclose(pf); // 关闭文件时自动刷新缓冲区 pf NULL; return 0; }关键结论缓冲区由 C 标准库管理默认大小由编译器决定通常 4KB/8KBfflush(stream)手动刷新缓冲区仅对输出流有效fclose关闭文件时自动刷新缓冲区因此必须关闭文件否则缓冲区数据可能丢失。9. 实战文件拷贝程序文本 / 二进制通用需求将source.txt源文件的内容拷贝到dest.txt目标文件支持文本和二进制文件。核心思路打开源文件读模式r或rb和目标文件写模式w或wb循环读取源文件数据写入目标文件关闭两个文件。代码实现#include stdio.h #include stdlib.h // 拷贝函数src_path源文件路径dest_path目标文件路径 void file_copy(const char* src_path, const char* dest_path) { // 打开源文件二进制读模式兼容文本和二进制文件 FILE* pfin fopen(src_path, rb); if (pfin NULL) { perror(打开源文件失败); exit(1); // 退出程序 } // 打开目标文件二进制写模式 FILE* pfout fopen(dest_path, wb); if (pfout NULL) { perror(打开目标文件失败); fclose(pfin); // 先关闭源文件避免内存泄漏 exit(1); } // 循环读写每次读1字节写1字节也可增大缓冲区提升效率 int ch 0; while ((ch fgetc(pfin)) ! EOF) { fputc(ch, pfout); } // 判定拷贝是否成功 if (feof(pfin)) { printf(拷贝成功\n); } else if (ferror(pfin)) { perror(拷贝失败); } // 关闭文件 fclose(pfin); fclose(pfout); pfin NULL; pfout NULL; } int main() { // 拷贝文本文件 file_copy(source.txt, dest.txt); // 拷贝二进制文件如图片、exe // file_copy(image.jpg, image_copy.jpg); return 0; }优化增大缓冲区提升效率每次读写 1 字节效率低可使用数组作为缓冲区每次读写 1024 字节// 优化版缓冲区大小1024字节 void file_copy_opt(const char* src_path, const char* dest_path) { FILE* pfin fopen(src_path, rb); FILE* pfout fopen(dest_path, wb); if (pfin NULL || pfout NULL) { perror(文件打开失败); exit(1); } char buf[1024] {0}; // 缓冲区 size_t ret 0; // 每次读1024字节返回实际读取的字节数 while ((ret fread(buf, 1, 1024, pfin)) ! 0) { fwrite(buf, 1, ret, pfout); // 写实际读取的字节数 } // 判定结果... fclose(pfin); fclose(pfout); }10. ⚠️ 常见文件操作易错点避坑指南未判断文件打开成功fopen返回NULL时直接操作导致崩溃打开方式错误只读模式r下写数据或只写模式w下读数据忘记关闭文件导致内存泄漏、缓冲区数据丢失关闭后未置空指针文件指针成为野指针后续误操作崩溃用char接收fgetc返回值无法区分EOF-1和字符0xFF255fgets的num参数过小导致字符串被截断未存储\0二进制文件用记事本打开看到乱码误以为写入失败正常现象忽略缓冲区未调用fflush或fclose数据未同步到硬盘。11. ✅ 文件操作最佳实践总结打开必判断fopen后必须检查返回值是否为NULL用perror打印错误关闭必执行读写完成后必须调用fclose且关闭后将指针置空模式要匹配读操作对应r/rb写操作对应w/wb追加对应a/ab二进制优先存储结构体、数组等复杂数据时优先用二进制模式高效、无转换缓冲区注意需要立即同步数据时调用fflush如日志输出结束必判定用feof和ferror区分 “文件尾” 和 “读错误”路径要正确文件路径若包含空格或特殊字符需用引号括起来如C:\my file.txt。文件操作是 C 语言的核心实用技能掌握它能让你的程序具备数据持久化能力无论是开发工具、日志系统还是数据处理程序都离不开文件操作。如果这篇博客帮到了你欢迎点赞收藏~