如何修复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
相关产品推荐
相关产品推荐

