C语言读取文本文件至结构体数组异常问题咨询
解决文件读取导致的结构体数据错位问题
看起来你踩了两个常见的C语言文件读取坑:一是完全误解了文件的格式结构,二是混用fscanf和fgets时没处理缓冲区残留的换行符,导致数据完全错位。咱们一步步解决这个问题:
问题根源分析
- 格式理解错误:你的文本文件里每个用户的所有字段都在同一行(比如
112 pass222 Ginge Akerwood Camella Homes 0922294812是一个用户的完整数据),但你的getData函数却错误地认为每个字段单独占一行,用fgets分别读取name、address、contactNum,直接导致读取的内容完全错位。 - 缓冲区残留问题:
fscanf读取完ID和password后,会把行尾的换行符留在输入缓冲区里,第一个fgets会直接读取这个空换行符,后续的fgets会读取下一行的错误内容,最终结构体数组的数据彻底混乱,出现你看到的ID被contactNum覆盖的异常输出。
完整修正方案
我们改用整行读取后拆分字段的方式,既避免缓冲区问题,又能正确处理带空格的name和address字段:
第一步:确保头文件定义正确
先确认Data.h里的字符串宏和结构体定义是正确的:
#define string10 char[11] #define string20 char[21] #define string30 char[31] #define string15 char[16] typedef struct userTag { int ID; string10 password; string20 name; string30 address; string15 contactNum; } userType;
第二步:修正getData和主程序代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Data.h" void display(userType u[], int nElem) { int i; for(i = 0; i < nElem; i++) { printf("=== User %d ===\n", i+1); printf("User ID: %d\n", u[i].ID); printf("Password: %s\n", u[i].password); printf("Name: %s\n", u[i].name); printf("Address: %s\n", u[i].address); printf("Contact Num: %s\n\n", u[i].contactNum); } } void getData(userType u[]) { int i = 0; FILE* fp_users = fopen("Users.txt", "r"); if (!fp_users) { perror("Failed to open Users.txt"); return; } char line[150]; // 足够容纳一行的所有内容 while (fgets(line, sizeof(line), fp_users) != NULL && i < 20) { // 移除行尾的换行符/回车符 size_t len = strlen(line); if (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) { line[len-1] = '\0'; } // 提取最后一个字段contactNum(无空格,最可靠的拆分点) char* contact_pos = strrchr(line, ' '); if (!contact_pos) continue; // 格式错误,跳过该行 strncpy(u[i].contactNum, contact_pos + 1, sizeof(u[i].contactNum) - 1); u[i].contactNum[sizeof(u[i].contactNum) - 1] = '\0'; *contact_pos = '\0'; // 截断字符串,去掉contactNum部分 // 提取ID和password(无空格字段,用sscanf直接读取) int temp_id; char temp_pass[11]; if (sscanf(line, "%d %s", &temp_id, temp_pass) != 2) continue; u[i].ID = temp_id; strncpy(u[i].password, temp_pass, sizeof(u[i].password) - 1); u[i].password[sizeof(u[i].password) - 1] = '\0'; // 提取name和address(带空格的字段) char* name_addr_start = line; // 跳过ID和password的内容,定位到name的起始位置 while (*name_addr_start != ' ') name_addr_start++; while (*name_addr_start == ' ') name_addr_start++; while (*name_addr_start != ' ') name_addr_start++; while (*name_addr_start == ' ') name_addr_start++; // 按结构体长度分配name和address:name最多20字符,剩下的给address strncpy(u[i].name, name_addr_start, sizeof(u[i].name) - 1); u[i].name[sizeof(u[i].name) - 1] = '\0'; // 跳过name后的空格,复制剩余内容到address char* addr_start = name_addr_start + strlen(u[i].name); while (*addr_start == ' ') addr_start++; strncpy(u[i].address, addr_start, sizeof(u[i].address) - 1); u[i].address[sizeof(u[i].address) - 1] = '\0'; i++; } fclose(fp_users); } int main(int argc, char *argv[]) { userType user[20]; memset(user, 0, sizeof(user)); // 初始化数组,避免垃圾值 getData(user); display(user, 2); // 你的文件只有2个用户,传2而非3 return 0; }
关键修正点说明
- 整行读取:用
fgets读取每一行,彻底避免fscanf和fgets混用的缓冲区残留问题。 - 可靠拆分contactNum:用
strrchr找到最后一个空格,拆分出无空格的contactNum,这是最稳定的拆分方式。 - 字段边界处理:手动定位name的起始位置,按结构体定义的长度分配name和address的内容,兼容带空格的字段。
- 错误检查:添加文件打开失败的检查,避免空指针访问;跳过格式错误的行,增强程序鲁棒性。
- 数组初始化:用
memset初始化结构体数组,避免未读取的用户字段出现垃圾值。
现在运行程序,就能正确输出每个用户的所有字段,ID不会再出现异常了。
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

