资讯动态

C语言文件操作完全指南:从流原理到工程避坑

发布时间:2026/10/17 5:41:51 来源:尧图企业网站定制
1. 先搞明白文件操作本质上是在操作什么不少初学者第一次接触C语言文件操作时脑子里最大的疑问是为什么往文件里写东西这么麻烦又是fopen又是fclose看起来比printf复杂得多。我个人觉得理解文件操作的关键一步是先想明白**文件在程序眼里长什么样**。程序运行时文件并不是一个可以直接倒进去的容器而是一条依据读取或写入位置不断移动的数据流。你把数据写进去这条流就把数据送到磁盘你想把数据读出来这条流就把磁盘上的内容送回内存。所以C语言里所有文件操作本质上都是围绕流stream在打转。FILE *fp这个几乎出现在每一个文件操作示例里的变量其实就是一个流的句柄。fopen干的事是建立程序与某个具体磁盘文件之间的联系并把这个联系的入口放在一个FILE结构体里。之后所有操作比如读、写、移动位置、关闭都必须通过这个入口来完成。打个比方这就像你去快递站取包裹。fopen是报上单号、确认包裹存在、拿到取件码fprintf/fread是根据取件码把包裹搬出来或搬进去fclose是取完包裹、确认无误、离开快递站。你要是把取件码弄丢了忘记保存FILE *或者取完包裹不关门忘记fclose迟早会出问题。很多教材喜欢用一个简单的例子引入文件操作#include stdio.h int main(void) { FILE *fp; fp fopen(test.txt, w); if (fp NULL) { printf(文件打开失败\n); return 1; } fputs(Hello, file!\n, fp); fclose(fp); return 0; }这段代码能跑通但绝不能止步于此。它只展示了文件操作最表面的流程实际工程里会遇到的模式问题、缓冲区问题、错误处理问题一个都没碰上。这篇文章就把这些没碰上的东西一次讲清楚。2. 一张表看懂打开模式的陷阱所有人第一次接触文件操作必然要先遇到fopen的第二个人参——打开模式。这一关要是没想透后面写出来的程序会忽好忽坏最典型的症状就是在Windows下好好的换到Linux就出乱码或者程序运行时正常关掉再跑却把上一次的数据覆盖了。先看最常用的六种模式模式含义文件不存在时文件存在时写指针初始位置r只读失败返回NULL正常打开文件开头w只写创建新文件清空内容文件开头a追加创建新文件保留内容文件末尾r读写失败返回NULL正常打开文件开头w读写创建新文件清空内容文件开头a追加读写创建新文件保留内容写入时在末尾读取时从开头这里有个新手最容易犯的错想读一个文件随手就用了w。一旦文件路径对了程序运行那一刻原文件内容已经被全部清空。等发现不对劲想补救数据已经没了。我见过不止一个同学调试程序时把几十行源代码存成txt当测试文件结果一次误操作全被清光。所以凡是带着读的需求第一反应应该是r或r绝对不要习惯性打w。再往下看还有一个被大多数人忽视的模式维度文本模式和二进制模式。基础写法里fopen(data.txt, rb)和fopen(data.txt, r)在Linux上几乎没有区别因为Linux本身就不区分文本和二进制。但在Windows上两者有实打实的差异——文本模式下写入\n会被转换成\r\n两个字节写到磁盘读的时候遇到\r\n会被转换回\n。二进制模式下这些转换都不发生一个字节进就一个字节出一个字节出就一个字节进。这意味着如果你在Windows下用文本模式写入一个文件每个换行实际占了两个字节如果换到Linux下用二进制模式读这个文件就会读到多余的\r。反过来同一个文件在Windows和Linux之间来回传递时如果没搞清楚它是用什么模式写的乱码和错位几乎是必然的。那么什么情况下用什么模式经验法则如下文本文件txt、 csv、日志、html等且只在当前平台读写用普通r/w/a即可交给系统帮你处理换行转换。需要跨平台共享或者文件里包含\n以外的控制字符强烈建议使用rb/wb/ab。二进制模式是所见即所得读出来的字节就是磁盘上的字节没有任何惊喜。涉及fseek计算偏移、读取结构体、读写固定长度记录等场景无条件选择二进制模式。只有二进制模式才能保证你按字节计算的偏移量是精确的。还必须提醒一件事有人以为r和w的区别只是能读又能写就随便选一个。这俩在实际使用上差别很大。w会立刻清空文件内容这在很多场景下是致命的r则保留原内容更适合先读进来改几个字段再写回去这类需求。一旦用错w原数据在fopen返回的那一刻就没了连后悔的机会都没有。打开模式这一关过完接下来再碰到读写函数就不至于因为文件内容莫名其妙消失而抓瞎了。3. 字符与字符串读写大多数人用错的那几个函数文件的基础读写操作看似简单但其实按操作单位不同可以分成好几个层次字符级、字符串行级、块级。每个层次都有对应的函数用错一层效果天差地别。3.1 逐字符读写最底层的读写是逐字符进行常用的读写函数分别是int fgetc(FILE *stream)和int fputc(int c, FILE *stream)。fgetc的返回值值得展开说说。它返回的是int而不是char这一设计是有意为之。因为文件读到末尾时需要返回一个特殊标志EOF也就是-1。如果函数返回值是char在常见的带符号char环境下-1和字符\xff也就是十进制255对应的那个字节会混为一谈——读到真正的\xff字节也会被误判成到末尾了。但反过来如果char是无符号类型\xff又能正常工作EOF又无法被返回。所以标准委员会干脆让fgetc返回int把正常字节和EOF标志彻底区分开。这个设计细节看起来不起眼却是C语言诸多设计决策中很典型的一环——在有限的类型系统里宁可牺牲一点直观性也要保证语义绝对清晰。实际操作中逐字符读写最常见的用途是文件复制。一个最简单的复制程序核心逻辑就是int ch; while ((ch fgetc(src)) ! EOF) { fputc(ch, dst); }注意这里ch必须声明为int不能是char。有人写成char ch程序在大部分ASCII文件上看起来正常一旦遇到包含高位字节的文件就会在某个特定字节处莫名提前结束。排查起来非常恼火因为问题不是每次都出现——只有字节值恰好等于0xFF时才会触发。这种坑属于你不理解fgetc的返回设计就永远想不到原因的类型。3.2 按行处理fgets是安全的选择字符级操作虽然基础但实际项目里用得更多的是读一行和写一行。写一行有fputs和fprintf读一行首选fgets。fgets的原型是char *fgets(char *s, int size, FILE *stream)它最多读取size-1个字符并在末尾自动补\0。这里的size参数是缓冲区总大小这个减一是安全边界的体现——无论如何缓冲区都不会被塞满到越界。跟scanf的%s相比fgets的缓冲区溢出风险被彻底堵死。但我见过不少新手摘抄示例时不假思索地写fgets(buf, sizeof(buf), fp)然后抱怨为什么我读到的总是缺东西或为什么这里多了一个换行。原因在于fgets会连换行符一起读进缓冲区。如果一行内容是hello读进来之后buf里实际存的是hello\n\0。这个换行符常常变成bug的源头。比如你逐行读文件每一行内容都塞进一个字符串数组然后拿去strcmp比较死活匹配不上打印出来却看不出差别——嗯确实是看不见的换行在捣乱。处理办法很简单读进来之后手动去掉末尾的换行size_t len strlen(buf); if (len 0 buf[len - 1] \n) { buf[len - 1] \0; }这个trim掉尾部换行的操作在几乎所有按行处理文件的程序里都是标配。懒惰一点的人可能忽略这一步结果就是数据明明看起来是对的程序逻辑却处处出错。3.3 fprintf和fscanf方便但必须留神fprintf和fscanf是printf/scanf的文件版本。fprintf的使用几乎和printf一样顺手fprintf(fp, name %s, age %d\n, name, age)。fscanf则是把从键盘读换成从文件读fscanf(fp, %d, num)。这俩函数用起来方便但有两个隐患。一是fscanf遇到空白字符空格、换行、制表符时的处理行为它跳过前导空白读到匹配的数据就停停下的位置正好是数据后面的第一个空白字符。跟fgets不同fscanf不会把换行符读进缓冲区这倒省了trim的麻烦。但反过来如果文件格式稍微有点变化fscanf的匹配就容易出岔子。最典型的就是用%d读整数之后文件位置停在了换行符前紧接着想用fgets再读下一行结果fgets先吃到的是那个残留的换行——拼接头尾时经常踩。第二个隐患在错误检测上。fscanf返回的是成功匹配并赋值的参数个数。如果一个%d %d只有第一个% d匹配成功返回值是1而不是2。你要是没检查返回值就继续用后一个变量程序会拿着一个未初始化的值继续执行。轻则计算错误重则指针操作崩溃。教训很简单凡是使用fscanf的地方养成先看返回值再用数据的习惯。格式简单、来源可控时fscanf还算是好帮手格式复杂、来源不可控时老老实实fgets sscanf后面这个组合要稳健得多。3.4 一个标准的逐行处理骨架把上面这些缝合起来一个稳妥的逐行处理文件的骨架大致长这样#include stdio.h #include string.h #define LINE_BUF_SIZE 1024 int main(void) { FILE *fp fopen(records.txt, r); if (fp NULL) { perror(打开文件失败); return 1; } char line[LINE_BUF_SIZE]; while (fgets(line, sizeof(line), fp) ! NULL) { // 去掉结尾换行 size_t len strlen(line); while (len 0 (line[len - 1] \n || line[len - 1] \r)) { line[--len] \0; } // 这里开始处理 line 的内容 printf(处理的记录: %s\n, line); } fclose(fp); return 0; }这里的while循环判断条件是fgets ! NULL原理是fgets成功读取返回缓冲区指针读到文件末尾返回NULL。这个写法比while(!feof(fp))要正确——feof标志要等读取操作真正碰到文件末尾之后才会置位如果你在循环开头判断feof最后一次循环往往会多处理一次空内容。这个经典错误我在后面专门展开讲。4. 块级读写fread和fwrite的正确用法文件操作从字符级、行级再往上走一层就到了块级读写。fread和fwrite是这层的核心它们和fgetc/fgets最大的区别在于以块为单位批量搬运数据适合操作结构体、数值数组和固定格式的数据文件。4.1 原型和参数含义size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);四个参数分别是存放/读取数据的内存地址、单个块的大小、块的数量、文件指针。返回值是成功读写的块数不是字节数。很多人容易忽略这个区别。比如fread(buf, 4, 100, fp)单个块4字节共100块总期望字节是400。如果文件实际只有200字节函数返回50——它读完50块就停住了。你要拿返回值乘上size总算出实际读取字节数。直接用返回值当字节数就错了整整4倍。反过来fwrite的返回值也类似。fwrite(buf, 1, 200, fp)在绝大多数情况下返回200但如果磁盘满了或写权限出问题返回值会小于200。你只检查非0还是不放心正确的检查方式是返回值等于nmemb。4.2 结构体直写快捷但有讲究块级读写最常见的使用场景是把一个结构体整个写进文件。比如typedef struct { char name[32]; int age; double score; } Student; Student stu {Alice, 20, 92.5}; fwrite(stu, sizeof(Student), 1, fp);这样做确实方便但背后有两个严重的前提假设。第一内存对齐问题。Student结构体在内存里不是紧巴巴挤在一起的。按默认对齐规则char name[32]占32字节int age通常要4字节对齐double score要8字节对齐。age后面、score前面很可能有填充字节。这些填充字节的内容是不确定的可能是任何值。你把这个结构体原封不动写进文件读出来时如果还是同一套编译器和同一台机器填充字节一致问题不大换个编译器、换台机器填充方式不同旧文件就读不出来了。第二字节序问题。int和double这类多字节数据类型在内存里按大端还是小端存储取决于CPU架构。在小端机器上保存的文件拿到大端机器上读所有数值型字段都会颠倒解析。对于自用的本地小工具这通常不是问题一旦文件要跨平台分发这种直写结构体的做法就是埋雷。所以我的经验法则是自己程序的临时数据文件、不跨平台、不跨编译器版本留档fwrite结构体完全没问题快且省事。需要长期保存、跨平台、与他人程序交换数据先转换成明确格式比如JSON、CSV或者自己定义逐字段序列化的二进制格式再写入。逐字段序列化虽然麻烦但换来的跨平台稳定性非常值。4.3 从文件读结构体时的边界判断用fread读结构体数组时有一个很容易踩的坑不清楚文件里到底存了多少条记录。最稳的办法是事先在文件开头写一个记录数字段或者按固定记录数循环读取。如果文件是动态增长的那就要依靠返回值判断。假设文件里有一堆Student结构体你想全部读进来Student s; while (fread(s, sizeof(Student), 1, fp) 1) { // 处理 s }这个写法是安全的。当fread返回0说明文件已经不够一个完整结构体了。注意这里不能用! 1做判断吗理论上也可以用但fread返回0或1两种值 1更清晰含义是成功读了一个块。若文件末尾只剩半个结构体比如上次写入时程序崩了只写了半个fread会返回0循环正常终止不会出现越界访问。这个边界处理比直接读取固定次数要健壮得多。4.4 写数组一个小的优化点写一个int数组时有人写成for (int i 0; i n; i) { fwrite(arr[i], sizeof(int), 1, fp); }正确是正确但性能上是浪费的。文件I/O的代价大头在系统调用和磁盘操作不在单次拷贝那几十个字节上。每写一个int就触发一次函数调用、一次内部缓冲处理比一次性把整个数组写进去慢一个数量级。更推荐fwrite(arr, sizeof(int), n, fp);一整个数组作为一块写入。小块合并成大批量在文件I/O里是底层性能优化的基本原则跟网络传输时尽量合并小包是同一个道理。喘口气想一想缓冲区存在的意义就是为了减少这种频繁的小动作。5. 文件内部游标rewind、fseek与ftell文件流不只有读和写两种操作还有一个容易被忽视但极其重要的属性文件位置指示器也就是你当前处于文件的哪个位置。换句更直白的话——文件数据是顺序访问的但你手里的指针随时可以移动。5.1 三个基础函数rewind(FILE *fp)把位置指示器移到文件开头并清除错误标志。等价于fseek(fp, 0, SEEK_SET)。fseek(FILE *fp, long offset, int whence)根据whence基准移动offset个字节。whence有三个取值SEEK_SET文件开头、SEEK_CUR当前位置、SEEK_END文件末尾。offset可正可负。ftell(FILE *fp)返回当前相对文件开头的字节偏移量。这三个函数组合起来能干很多看似不可能的事情。5.2 获取文件大小最常见的用法是拿到文件总字节数。做法很简单fseek(fp, 0, SEEK_END); long file_size ftell(fp); rewind(fp);先把游标移到文件末尾ftell返回的就是从开头到末尾的距离也就是文件大小。然后再rewind回开头准备真正的读取。这个技巧简洁但要留意它的适用边界。ftell返回的是long类型在32位long的环境下最大只能表示约2GB。超过2GB的文件这个值会溢出。遇到超大文件更可靠的做法是用fseek的SEEK_END定位后配合平台相关的64位接口确认大小。但就基础教程和日常小工具来说这个做法完全够用。还要留意一点在文本模式下ftell返回的偏移量不等于字符数。因为Windows文本模式下换行\n在磁盘是\r\n两个字节ftell返回的偏move量是按转换前的逻辑位置来算的直接拿它做算术偏移可能产生偏差。所以在涉及fseek/ftell做精确定位时务必使用二进制模式。这一点我在上面打开模式那节提过到这里体会就更深刻了——文本模式下不要随意用fseek乱跳这是很多老鸟总结出来的戒律。5.3 随机读取按索引访问记录如果数据文件里存的是固定长度的结构体记录fseek可以做到按索引定位随机访问这是顺序扫描无法比拟的效率提升。比如每条记录恰好是100字节你想读取第5条记录索引从0开始可以这样做typedef struct { char id[20]; char name[40]; double score; } Record; Record rec; fseek(fp, 5 * sizeof(Record), SEEK_SET); fread(rec, sizeof(Record), 1, fp);fseek会直接跳到第500字节处fread随即读取一条记录。这个定位是O(1)的不依赖前面记录的数量。文件里有十万条记录你想读最后一条依然是瞬间完成不需要从头扫十万条。注意这个技巧的前提是结构体大小固定。如果你的结构体里有变长内容比如char *name指向堆内存那sizeof(Record)只是指针大小真正的内容在别处fseek就失效了。变长记录的随机访问需要额外建索引表那就是另一套方案了不在基础篇范围。5.4 更新文件中的某条记录随机访问还有个更实际的用途修改文件中指定位置的内容而不必全部重写。需求场景是一个配置工具要修改第3条配置记录的某字段其他不动。思路是先r打开文件fseek定位到目标记录偏移fread读出旧内容修改完成后再fseek到同一位置fwrite写回。关键在于读写之间如果打算切换方向必须插入一次fseek或rewind。C标准规定输出之后不能立刻输入除非遇到EOF输入之后也不能立刻输出除非调用fseek等定位函数。这是因为底层缓冲区的读写方向切换需要同步。你不插入fseek即使逻辑上位置对了读或写也会不预期地失败。代码示例FILE *fp fopen(data.bin, rb); if (fp NULL) { /* 处理失败 */ } Record rec; // 定位到索引 2第3条记录 fseek(fp, 2 * sizeof(Record), SEEK_SET); fread(rec, sizeof(Record), 1, fp); // 修改字段 rec.score 99.0; // 关键读写切换前必须重新定位 fseek(fp, 2 * sizeof(Record), SEEK_SET); fwrite(rec, sizeof(Record), 1, fp); fclose(fp);这里第二次fseek不仅把文件游标移回原位还起到刷新读写状态的作用。没有这一行fwrite可能因状态不清而写入失败或者写入到错误的位置。这个细节我在实际开发中踩过排查了大半天。6. 那些坑我一次性给你列全文件操作这块的坑很经典每个写过几年C的人基本都遇到过。把它们汇总起来方便新手对照自查。6.1 检查不到位的fopen返回值fopen失败会返回NULL但很多示例代码都忽略了检查。实际环境里文件路径拼错、权限不足、磁盘充满、共享文件被占用任何一条都会让fopen返回NULL。后续所有读写操作在NULL指针上执行轻则崩溃重则未定义行为。合格的程序长这样FILE *fp fopen(path, r); if (fp NULL) { perror(fopen); return -1; }perror自带错误描述比printf 打开失败 更有诊断价值。6.2 用feof判断循环结束多读了一次这是新手经典错误。写成while (!feof(fp)) { fgets(buf, sizeof(buf), fp); // 处理 buf }为什么会多处理一次因为feof标志只有在读操作尝试越过文件末尾之后才被置位。fgets读到末尾前最后一次成功读取后feof还没置位循环再次进入尝试读取fgets返回NULLbuf内容可能是上一次的旧数据也可能未被修改然后处理buf这段代码却执行了——哪怕这次实际上没有有效新数据。正解是用读取函数的返回值做判断fgets返回NULL、fread返回0、fgetc返回EOF不要用feof做循环控制。feof只适合在循环结束后确认确实是读到了末尾而不是出错。6.3 fclose的返回值被忽略多数人fclose完事就溜不看返回值。但fclose可能在真正把缓冲区的数据刷到磁盘时失败——磁盘满了、写入途中IO错误。特别是使用fwrite大批量写数据时浅层缓冲区看似写入成功实际落盘是在fclose时才完成。忽略fclose的返回值等于放弃了最后一道写入失败的检查。写成这样更专业if (fclose(fp) ! 0) { perror(fclose); }6.4 刷新时机setbuf与fflush文件输出默认是全缓冲数据先攒在内存缓冲区里缓冲区满或主动刷新时才真正写盘。这是性能优化的手段。但副作用是程序运行时你去看文件内容可能还不在磁盘上程序异常崩溃未经fclose缓冲区里的数据可能全部丢失。如果希望关键数据及时落盘可以主动调用fflush(fp)。每次写完重要数据就fflush一下牺牲一点性能换鲁棒性。配置文件、日志文件、数据库记录这类要求高可靠性的场景强烈建议。还有个与之相关的冷知识点程序正常结束return 0或main里的exit退出流程会自动清理所有打开的FILE包括flush缓冲区。但如果你用_exit终止进程缓冲区内容会直接丢光。写游戏存档、计费日志这种对落盘时机敏感的程序特别要注意这一条。6.5 文本模式跨平台换行混乱这一条在第二部分已经详细说了原理这里只补一个实际场景某个配置文件在Windows下生成用a模式追加Windows文本模式的\n文件实际存的是\r\n。把文件拷到Linux上用fgets按行读每条记录尾巴都会多带一个\r。你如果没做\r trim字符串比较、拼接、序列化全都可能出问题。所以我自己的习惯是凡是跨平台换过文件的场景要么全部用二进制模式要么程序里统一处理\r\n。别指望文本模式会自动处理它只在同一平台内部有效跨平台的边界是隐形的。6.6 忘记处理ferrorfread/fwrite返回0不一定就是读到了EOF也可能是真正的IO错误。使用ferror(fp)可以区分返回非0表示出错。有些时候文件指针本身是好的但设备出问题比如U盘被拔出这时程序不应静静退出而应给出明确的错误信息。稳健的读取循环while (fgets(buf, sizeof(buf), fp) ! NULL) { // 处理 } if (ferror(fp)) { perror(读取过程中出错); }这样正常读完和读到一半出问题就被区分开了。7. 一个手把手的综合示例把上面所有知识点串起来我写一个比较综合的示例一个学生成绩记录的小工具支持写入记录、按索引查询、按索引修改。它涵盖了文本与二进制的选择、fwrite/fread的块读写、fseek/rewind的定位操作、错误检查、以及循环结束条件。直接复制就能编译运行。#include stdio.h #include string.h #define RECORD_SIZE 100 typedef struct { char id[20]; // 学号 char name[30]; // 姓名 float score; // 成绩 } Student; // 初始化文件如果不存在就创建一个新的 FILE *open_db(const char *path, const char *mode) { FILE *fp fopen(path, mode); if (fp NULL) { perror(fopen); return NULL; } return fp; } // 写一条记录到文件末尾 int append_record(FILE *fp, const Student *stu) { // 移到文件末尾 fseek(fp, 0, SEEK_END); size_t written fwrite(stu, sizeof(Student), 1, fp); if (written ! 1) { fprintf(stderr, 写入记录失败\n); return -1; } // 立即刷盘确保数据落地 if (fflush(fp) ! 0) { fprintf(stderr, 刷新失败\n); return -1; } return 0; } // 按索引读取记录索引从 0 开始 int read_record(FILE *fp, int index, Student *stu) { if (fseek(fp, index * sizeof(Student), SEEK_SET) ! 0) { return -1; } size_t n fread(stu, sizeof(Student), 1, fp); return (n 1) ? 0 : -1; } // 按索引修改记录 int update_record(FILE *fp, int index, const Student *stu) { // 第一个fseek用于检查索引是否合法 if (fseek(fp, index * sizeof(Student), SEEK_SET) ! 0) { return -1; } Student dummy; size_t n fread(dummy, sizeof(Student), 1, fp); if (n ! 1) { return -1; // 索引超出范围 } // 回到目标位置写新数据 if (fseek(fp, index * sizeof(Student), SEEK_SET) ! 0) { return -1; } size_t written fwrite(stu, sizeof(Student), 1, fp); if (written ! 1) { return -1; } if (fflush(fp) ! 0) { return -1; } return 0; } int main(void) { const char *dbpath students.dat; FILE *fp open_db(dbpath, rb); if (fp NULL) { // 文件不存在时用 wb 创建一个空的 fp open_db(dbpath, wb); if (fp NULL) { return 1; } } // 追加三条记录 Student s1 {20240001, Zhang San, 85.5f}; Student s2 {20240002, Li Si, 92.0f}; Student s3 {20240003, Wang Wu, 76.5f}; append_record(fp, s1); append_record(fp, s2); append_record(fp, s3); // 读取索引 1 的记录并打印 Student tmp; if (read_record(fp, 1, tmp) 0) { printf(索引为 1 的记录: %s, %s, %.1f\n, tmp.id, tmp.name, tmp.score); } // 修改索引 0 的记录 Student newStu {20240099, Zhao Liu, 88.8f}; if (update_record(fp, 0, newStu) 0) { printf(索引 0 已更新。\n); } else { printf(更新失败可能是索引越界。\n); } // 最终打印整个文件的内容 rewind(fp); int idx 0; while (fread(tmp, sizeof(Student), 1, fp) 1) { printf(记录 %d: %s, %s, %.1f\n, idx, tmp.id, tmp.name, tmp.score); } if (ferror(fp)) { perror(读取文件时出错); } fclose(fp); return 0; }这个程序里所有关键操作都做了返回检查fread/fwrite返回值被认真对待update_record里先探测索引是否越界再写入避免写到文件末尾之外的位置。运行两次还能验证追加逻辑——第二次运行第一次创建的记录还在新记录继续追加这正是rb模式加append到末尾的功劳。8. 我最后想多说一句文件操作是C语言里最工程化的内容之一。比起指针和链表它的概念不算深但恰恰因为简单很多人不仔细研究等到真正写项目时才被各种边界问题套住。我个人的体会是把打开模式、返回值检查、缓冲刷新这三件事刻进肌肉记忆文件操作就成功了八成。剩下的fseek定位和块级读写都是围绕这三件事展开的应用。建议你自己动手写一个把文本文件内容反转输出的小工具或者做一个二进制记录文件的增删改查比背一百遍函数原型都有用。踩上几个坑自然就记住了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑