C语言使用fgets/sscanf读取指定格式文件到结构体数组的方法
格式化文本文件读取方案(fgets+sscanf实现)
原有代码的错误点
你的写法存在几处会直接导致解析失败的问题:
- 循环条件
while(!feof(fp))是C语言文件读取的经典错误:feof仅在读取操作已经触发文件结束标识后才会返回真值,用它做循环判断会导致最后一行内容被重复解析一次。 - 多次独立调用
sscanf的逻辑完全不成立:每次调用sscanf都会从传入字符串的起始位置重新开始匹配,四次调用全部从行首读值,根本不会按顺序向后解析字段,最终只会拿到第一个匹配的整数值,后续字段全部解析错误。 - 菜名字段匹配逻辑错误:直接用
%[^\n]会读取从行首到换行符的全部内容,根本无法拆分出后面的重量、卡路里两个数值字段。 - 未处理第一行表头:文件第一行是列标题,不是有效数据,直接解析会触发类型匹配错误,写入脏数据。
fgets和fscanf的选择
优先选择fgets逐行读取+sscanf行内解析的组合,原因如下:
- 逐行读取不会因为某一行格式匹配失败导致文件流指针错位,错误定位和处理成本更低
- 直接用
fscanf读文件流时,一旦某字段匹配失败,很难定位出错位置,处理带空格的中间文本字段时极易出现跨行长匹配的问题 - 行内容先读到内存缓冲区后,可以灵活调整解析规则,适配这类中间字段带空格、前后为固定数值的非严格CSV格式。
正确实现代码
解析逻辑:每行结构为「整数序号 + 含空格的菜名文本 + 整数重量 + 整数卡路里」,菜名部分不含数字,因此用非数字匹配集抓取菜名,单次sscanf完成全字段解析,同时做边界检查和错误处理。
#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX_LINE_LEN 100 #define MAX_ENTRY_NUM 30 #define MAX_NAME_LEN 50 // 定义存储结构体 struct FoodEntry { int no; char name[MAX_NAME_LEN]; int weight_gram; int calorie; }; int main() { FILE *fp = fopen("food_data.txt", "r"); if (fp == NULL) { perror("Open file failed"); return EXIT_FAILURE; } char line_buf[MAX_LINE_LEN]; struct FoodEntry food_list[MAX_ENTRY_NUM]; int entry_cnt = 0; // 读取并丢弃第一行表头 if (fgets(line_buf, MAX_LINE_LEN, fp) == NULL) { fclose(fp); return EXIT_FAILURE; } // 正确循环判断:fgets返回NULL代表读到文件尾或读取出错 while (fgets(line_buf, MAX_LINE_LEN, fp) && entry_cnt < MAX_ENTRY_NUM) { // 单次sscanf顺序解析所有字段 // %49[^0-9] 限制最多读49个非数字字符,预留1字节给字符串结束符,防止缓冲区溢出 int matched = sscanf(line_buf, "%d %49[^0-9] %d %d", &food_list[entry_cnt].no, food_list[entry_cnt].name, &food_list[entry_cnt].weight_gram, &food_list[entry_cnt].calorie ); // 校验匹配字段数,不足4个说明该行格式异常 if (matched != 4) { fprintf(stderr, "Parse error at line %d: %s", entry_cnt + 2, line_buf); break; } // 去除菜名末尾附带的多余空白字符 size_t name_len = strlen(food_list[entry_cnt].name); while (name_len > 0 && (food_list[entry_cnt].name[name_len-1] == ' ' || food_list[entry_cnt].name[name_len-1] == '\t')) { food_list[entry_cnt].name[name_len-1] = '\0'; name_len--; } entry_cnt++; } fclose(fp); // 测试输出解析结果 for (int i = 0; i < entry_cnt; i++) { printf("%-3d %-25s %4dg %4dkcal\n", food_list[i].no, food_list[i].name, food_list[i].weight_gram, food_list[i].calorie ); } return EXIT_SUCCESS; }
通用注意要点
- 永远不要用
feof作为文件读取的循环判断条件,必须以读取函数(fgets/fread/fscanf等)的返回值作为读取结束的判断依据。 - 用
sscanf解析多字段时,尽量单次调用完成所有字段匹配,不要多次从头解析同一字符串。 - 用
%[]匹配集读字符串时,必须加最大宽度限制,宽度值为字符数组长度减1,避免缓冲区溢出。 - 所有解析操作必须检查返回值,确认匹配到的字段数和预期一致后再使用解析出的数据,避免脏数据导致程序异常。
内容的提问来源于stack exchange,提问作者user512512af
相关产品推荐
相关产品推荐

