1. 案发现场代码与现象我们先来还原一下问题场景。这段代码的意图很简单定义一个学生结构体将其写入文件然后再读出来打印。原始代码#include stdio.h struct stu { char name[20]; int age; int math; }; int main() { struct stu s1 {zhangsan, 23, 90}; FILE* ptr fopen(dest.bin, w); // 以读写方式打开文本文件 if (ptr NULL) { perror(fopen); return 1; } struct stu s2 { 0 }; // 初始化为0 // 1. 写入数据使用逗号分隔 fprintf(ptr, %s,%d,%d, s1.name, s1.age, s1.math); rewind(ptr); // 将文件指针移回开头 // 2. 读取数据试图用 %s 匹配逗号前的内容 fscanf(ptr, %s,%d,%d, s2.name, s2.age, s2.math); // 3. 打印结果 printf(%s,%d,%d\n, s2.name, s2.age, s2.math); fclose(ptr); return 0; }运行结果异常zhangsan,23,90,0,0现象分析前三个值zhangsan,23,90是s1的数据或者是写入的内容。后两个值0,0说明s2的age和math读取失败保持了初始化时的0。甚至s2.name可能包含了不该有的字符取决于具体编译器的实现细节。2. 深度解析罪魁祸首%s这个错误的根源在于对fscanf中%s转换说明符的停止条件理解有误。核心知识点%s是如何工作的在scanf家族函数中%s用于读取字符串。它的规则非常死板跳过前导空白字符空格、换行、制表符。开始读取非空白字符。停止读取只有当它再次遇到空白字符时才会认为字符串结束了。关键点来了逗号,不是空白字符灾难现场还原假设文件里的实际内容是zhangsan,23,90当你执行fscanf(ptr, %s,%d,%d, ...)时程序内部发生了这样的逻辑冲突执行%s程序开始读取z,h,a... 一直到n。接着遇到了逗号,。因为逗号不是空格%s认为字符串还没结束它会继续把逗号也读进name数组里。紧接着它看到2也不是空格继续读最终%s可能会把zhangsan,23,90全部当作一个长字符串塞进s2.name中直到遇到文件结尾或缓冲区溢出。后续匹配失败由于%s已经把后面的数字都“吃”掉了或者文件指针位置已经错乱。接下来的%d找不到合法的整数起始位置或者格式串中的字面量逗号无法匹配。结果读取中断age和math赋值失败保持为0。3. 解决方案针对这个问题我们有三种不同层级的解决思路。方案一修改分隔符最简单推荐新手既然%s只认空格那我们就投其所好。将写入和读取的分隔符改为空格。// 写入用空格代替逗号 fprintf(ptr, %s %d %d, s1.name, s1.age, s1.math); // 读取scanf 会自动处理空格 fscanf(ptr, %s %d %d, s2.name, s2.age, s2.math);方案二使用“扫描集”%[^,]进阶技巧如果你必须使用 CSV逗号分隔格式你需要显式地告诉fscanf“请读取字符直到遇到逗号为止”。这需要使用%[...]语法其中^表示“取反”即“除了...以外”。// %[^,] 的意思是读取任意字符直到遇到逗号停止逗号本身不会被读入 // 注意格式串里的逗号用来消耗掉文件里的那个逗号 fscanf(ptr, %[^,],%d,%d, s2.name, s2.age, s2.math);方案三二进制读写专业做法强烈推荐对于结构体这种包含多种数据类型的复杂对象使用文本读写fprintf/fscanf不仅容易出错而且效率低。最稳妥、最高效的方式是直接进行二进制块读写。这样不需要关心分隔符也不用担心字符串里的特殊字符。完整示例代码#include stdio.h #include string.h struct stu { char name[20]; int age; int math; }; int main() { // 1. 准备数据 struct stu s1 {zhangsan, 23, 90}; struct stu s2 { 0 }; // 用于接收读取的数据 // 2. 打开文件 // 注意二进制读写必须使用 wb (写) 和 rb (读) 模式 // 如果要同时读写可以使用 wb 或 rb FILE* ptr fopen(student.dat, wb); if (ptr NULL) { perror(fopen failed); return 1; } // 3. 写入 (fwrite) // 参数含义(源地址, 单个元素大小, 元素个数, 文件指针) size_t write_count fwrite(s1, sizeof(struct stu), 1, ptr); if (write_count ! 1) { printf(写入失败!\n); } else { printf(写入成功: %s, %d, %d\n, s1.name, s1.age, s1.math); } // 4. 移动指针 // 写入后指针在文件末尾必须移回开头才能读取 rewind(ptr); // 5. 读取 (fread) // 参数含义(目标地址, 单个元素大小, 元素个数, 文件指针) size_t read_count fread(s2, sizeof(struct stu), 1, ptr); if (read_count ! 1) { printf(读取失败!\n); } else { // 6. 验证结果 printf(读取成功: %s, %d, %d\n, s2.name, s2.age, s2.math); } fclose(ptr); return 0; }二进制读写的优势精确映射内存里结构体长什么样文件里就存成什么样完全一致。无需解析不需要像文本那样去解析逗号、空格速度极快。安全性高不会出现因为名字里带了空格或逗号导致读取错位的问题。总结陷阱fscanf的%s遇到逗号不会停会导致后续数据读取错位。文本处理如果非要用文本请用空格分隔或使用%[^,]技巧。最佳实践对于结构体数据的持久化存储请优先选择fwrite和fread进行二进制操作。