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

如何修复fscanf读取含3/4字段的逗号分隔患者数据的问题?

问题描述

我有一个存储患者详细信息的逗号分隔文本文件,部分条目包含3个值,其余条目包含4个值。文件内容如下:

1032,Pugsley Yanson,CELL,3048005191
1048,Banjo Codi,TBD,
1056,Lettuce Peas,WORK,7934346809

我编写了如下C语言代码,将数据读取到结构体中:

struct Phone
{
    char description[PHONE_DESC_LEN];
    char number[PHONE_LEN];
};
// Data type: Patient 
struct Patient
{
    int patientNumber;
    char name[NAME_LEN];
    struct Phone phone;
};

void importPatients(const char* datafile, struct Patient patients[], int max){
FILE *fp = fopen(datafile, "r");
int i = 0;
int read = 0;
while (!feof(fp) && i < max){
    read = fscanf(fp,"%d,%14[^,],%4[^,],%10[^,]\n",&patients[i].patientNumber,patients[i].name,patients[i].phone.description,patients[i].phone.number);
    if(read == 0 && !feof(fp)){
        fclose(fp);
        return;
    }
    i++;
}
   fclose(fp);
}

该代码在读取含4个值的条目时运行正常,但遇到如1048,Banjo Codi,TBD,这类含3个值的条目时失效。请问该如何修复此问题,或是有更优的解决方法?


修复方案及优化思路

问题根源

你的fscanf格式串强制要求读取4个字段,当遇到只有3个值的条目(最后一个逗号后无内容)时,%10[^,]会匹配失败,导致文件指针停滞,后续读取陷入混乱,甚至触发死循环(feof仅在读取到文件末尾时才会置位,匹配失败不会触发它)。

修复方法1:调整格式串适配两种字段情况

修改格式串,允许最后一个字段为空,同时根据fscanf的返回值做分支处理:

#include <string.h>

void importPatients(const char* datafile, struct Patient patients[], int max){
    FILE *fp = fopen(datafile, "r");
    if (!fp) return; // 新增文件打开失败判断,避免空指针操作
    int i = 0;
    int read = 0;
    while (i < max) {
        // 清空当前患者的电话字段,避免残留旧数据
        memset(&patients[i].phone, 0, sizeof(struct Phone));
        // 调整格式串,最后一个字段匹配到换行符为止(允许为空)
        read = fscanf(fp,"%d,%14[^,],%4[^,],%10[^\n]\n",
            &patients[i].patientNumber,
            patients[i].name,
            patients[i].phone.description,
            patients[i].phone.number);
        
        if (read == EOF) break; // 读取到文件末尾,退出循环
        
        // 仅读取到3个字段,说明最后一个逗号后无内容
        if (read == 3) {
            char newline[2];
            fscanf(fp, "%1[\n]", newline); // 手动跳过换行符
        } 
        // 字段数不足3,视为无效条目,跳过当前行
        else if (read < 3) {
            char skip_buf[256];
            fgets(skip_buf, sizeof(skip_buf), fp);
            continue;
        }
        i++;
    }
    fclose(fp);
}

核心改动:

  • 将最后一个字段的匹配规则从%10[^,]改为%10[^\n],支持匹配空内容
  • 根据read返回值处理不同字段数的情况,保证文件指针正常前进
  • 新增文件打开失败判断,以及读取前清空字段的操作,提升代码健壮性

更优方案:逐行读取再分割字段(推荐)

fscanf处理CSV格式的灵活性很差,遇到空字段或特殊字符时容易出问题。更可靠的方式是用fgets逐行读取整行内容,再手动分割字段:

#include <string.h>

void importPatients(const char* datafile, struct Patient patients[], int max){
    FILE *fp = fopen(datafile, "r");
    if (!fp) return;
    int i = 0;
    char line_buf[256]; // 假设单行数据长度不超过256字节
    while (i < max && fgets(line_buf, sizeof(line_buf), fp)) {
        // 清空当前患者的所有数据
        memset(&patients[i], 0, sizeof(struct Patient));
        
        // 分割第一个字段:患者编号
        char *token = strtok(line_buf, ",");
        if (!token) continue;
        patients[i].patientNumber = atoi(token);
        
        // 分割第二个字段:姓名
        token = strtok(NULL, ",");
        if (!token) continue;
        strncpy(patients[i].name, token, NAME_LEN - 1); // 预留字符串结束符位置
        
        // 分割第三个字段:电话描述
        token = strtok(NULL, ",");
        if (!token) continue;
        strncpy(patients[i].phone.description, token, PHONE_DESC_LEN - 1);
        
        // 分割第四个字段:电话号码(允许为空)
        token = strtok(NULL, "\n"); // 用换行符分割,避免读取到换行符
        if (token) {
            // 去掉字符串末尾的换行符
            token[strcspn(token, "\n")] = '\0';
            strncpy(patients[i].phone.number, token, PHONE_LEN - 1);
        }
        i++;
    }
    fclose(fp);
}

这种方式的优势:

  • 完全适配任意位置的空字段,扩展性更强
  • 逻辑更直观,调试和维护更方便
  • 能更好地处理包含特殊字符的字段(比如带空格的姓名)

额外注意事项

  • 定义字符串长度宏时,要预留1字节给字符串结束符'\0',比如电话号码是10位的话,PHONE_LEN应该设为11
  • 所有字符串拷贝操作使用strncpy,避免缓冲区溢出
  • 可以增加更多错误处理逻辑(比如字段转换失败的情况),进一步提升代码稳定性

内容的提问来源于stack exchange,提问作者Manraj Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 15:20:38