You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:03:29