文本文件存入结构体数组遇单类型宝可梦解析错误求助
问题分析与解决
问题现象
尝试将宝可梦文本数据存入结构体数组,文本示例:
#001 Bulbasaur Grass Poison #002 Ivysaur Grass Poison #003 Venusaur Grass Poison #004 Charmander Fire #005 Charmeleon Fire #006 Charizard Fire Flying
宝可梦可能有1或2种属性,处理单属性宝可梦时出现数据错位,输出异常:
#001 Bulbasaur Grass Poison #002 Ivysaur Grass Poison #003 Venusaur Grass Poison #004 Charmander Fire #005 Charmeleon Fire #006 Charizard Fire Flying #007 Squirtle Water #008 Wartortle Water #009 Blastoise Water #010
相关代码
结构体定义
typedef struct pokemon { int ID; char *number; char *name; char *type1; char *type2; } Pokemon; struct Trainer { char *firstName; char *lastName; Pokemon *pokemonsInBank; Pokemon pokemonHeldByTheTrainer[6]; } Trainer; struct pokemonDataBase { Pokemon *pokemonsDB; int numOfPokemon; } pokemonDataBase;
数据加载函数
void loadPokemonsToDB() { char numBuffer[maxSize]; char nameBuffer[maxSize]; char typeBuffer[maxSize]; char type2Buffer[maxSize]; int i = 0; char check[maxSize]; FILE *fp = fopen("Pokemons.txt", "r"); if (fp == NULL) { printf("Error opening the file\n"); exit(4); } pokemonDataBase.pokemonsDB = (Pokemon *)malloc((countPok()) * sizeof(Pokemon)); if (pokemonDataBase.pokemonsDB == NULL) { printf("Memory Error\n"); exit(4); } for (int i = 0; i < countPok(); i++) { fscanf(fp, "%s", numBuffer); pokemonDataBase.pokemonsDB[i].number = (char *)malloc((strlen(numBuffer) + 1) * sizeof(char)); if (pokemonDataBase.pokemonsDB[i].number == NULL) { for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].number); } printf("Memory Error at index %d", i); exit(4); } strcpy(pokemonDataBase.pokemonsDB[i].number, numBuffer); fscanf(fp, "%s", nameBuffer); pokemonDataBase.pokemonsDB[i].name = (char *)malloc((strlen(nameBuffer) + 1) * sizeof(char)); if (pokemonDataBase.pokemonsDB[i].name == NULL) { for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].name); } printf("Memory Error at index %d", i); exit(4); } strcpy(pokemonDataBase.pokemonsDB[i].name, nameBuffer); fscanf(fp, "%s", typeBuffer); pokemonDataBase.pokemonsDB[i].type1 = (char *)malloc((strlen(typeBuffer) + 1) * sizeof(char)); if (pokemonDataBase.pokemonsDB[i].type1 == NULL) { for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].type1); } printf("Memory Error at index %d", i); exit(4); } strcpy(pokemonDataBase.pokemonsDB[i].type1, typeBuffer); fscanf(fp, "%s", type2Buffer); pokemonDataBase.pokemonsDB[i].type2 = (char *)malloc((strlen(type2Buffer) + 1) * sizeof(char)); if (pokemonDataBase.pokemonsDB[i].type2 == NULL) { for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].type2); } printf("Memory Error at index %d", i); exit(4); } strcpy(pokemonDataBase.pokemonsDB[i].type2, type2Buffer); } }
问题根源
fscanf("%s") 以空白符(空格、换行、制表符)为分隔符读取内容,当遇到单属性宝可梦的行时,读取完type1后,fscanf("%s", type2Buffer)会跳过换行符,直接读取下一行的编号(比如#005)作为type2,导致后续所有行的读取位置全部错位,最终输出混乱。
修复方案
改为逐行读取整行内容,再解析每行的字段,避免跨行列读取:
#include <string.h> #include <stdlib.h> #include <stdio.h> #define maxSize 256 // 统计文件中宝可梦行数的函数 int countPok() { FILE *fp = fopen("Pokemons.txt", "r"); if (!fp) return 0; int count = 0; char line[maxSize]; while (fgets(line, maxSize, fp)) { count++; } fclose(fp); return count; } void loadPokemonsToDB() { char line[maxSize]; FILE *fp = fopen("Pokemons.txt", "r"); if (fp == NULL) { printf("Error opening the file\n"); exit(4); } int totalPokemon = countPok(); pokemonDataBase.pokemonsDB = malloc(totalPokemon * sizeof(Pokemon)); if (pokemonDataBase.pokemonsDB == NULL) { printf("Memory Error\n"); fclose(fp); exit(4); } pokemonDataBase.numOfPokemon = totalPokemon; for (int i = 0; i < totalPokemon; i++) { // 读取整行内容 if (!fgets(line, maxSize, fp)) { // 读取失败,清理已分配的所有内存 for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].number); free(pokemonDataBase.pokemonsDB[j].name); free(pokemonDataBase.pokemonsDB[j].type1); if (pokemonDataBase.pokemonsDB[j].type2) { free(pokemonDataBase.pokemonsDB[j].type2); } } free(pokemonDataBase.pokemonsDB); fclose(fp); printf("Failed to read line %d\n", i); exit(4); } // 去掉行尾的换行符 size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') { line[len-1] = '\0'; } char numBuffer[maxSize]; char nameBuffer[maxSize]; char type1Buffer[maxSize]; char type2Buffer[maxSize] = {0}; // 尝试匹配4个字段,返回实际读取到的字段数 int fieldsRead = sscanf(line, "%s %s %s %s", numBuffer, nameBuffer, type1Buffer, type2Buffer); // 分配并复制编号 pokemonDataBase.pokemonsDB[i].number = strdup(numBuffer); if (!pokemonDataBase.pokemonsDB[i].number) goto mem_error; // 分配并复制名字 pokemonDataBase.pokemonsDB[i].name = strdup(nameBuffer); if (!pokemonDataBase.pokemonsDB[i].name) goto mem_error; // 分配并复制第一个属性 pokemonDataBase.pokemonsDB[i].type1 = strdup(type1Buffer); if (!pokemonDataBase.pokemonsDB[i].type1) goto mem_error; // 处理第二个属性:仅读取到3个字段时,设为NULL if (fieldsRead == 3) { pokemonDataBase.pokemonsDB[i].type2 = NULL; } else { pokemonDataBase.pokemonsDB[i].type2 = strdup(type2Buffer); if (!pokemonDataBase.pokemonsDB[i].type2) goto mem_error; } // 提取ID(去掉编号前的#) pokemonDataBase.pokemonsDB[i].ID = atoi(numBuffer + 1); continue; mem_error: // 清理当前元素已分配的内存 free(pokemonDataBase.pokemonsDB[i].number); free(pokemonDataBase.pokemonsDB[i].name); free(pokemonDataBase.pokemonsDB[i].type1); // 清理之前所有元素的内存 for (int j = 0; j < i; j++) { free(pokemonDataBase.pokemonsDB[j].number); free(pokemonDataBase.pokemonsDB[j].name); free(pokemonDataBase.pokemonsDB[j].type1); if (pokemonDataBase.pokemonsDB[j].type2) { free(pokemonDataBase.pokemonsDB[j].type2); } } free(pokemonDataBase.pokemonsDB); fclose(fp); printf("Memory Error at index %d\n", i); exit(4); } fclose(fp); }
内存分配优化建议
- 避免重复调用
countPok():原代码多次调用该函数,会重复打开/关闭文件,建议先调用一次存到变量中,减少IO开销。 - 完善内存清理逻辑:原代码内存分配失败时,仅释放当前字段的历史内存(比如
name分配失败时未释放number),修复后的代码会清理所有已分配资源,避免内存泄漏。 - 用
strdup()简化代码:strdup()自动分配足够内存并复制字符串,比手动malloc+strcpy更简洁;若环境不支持该函数,可自行实现:char* strdup(const char* s) { char* res = malloc(strlen(s)+1); if(res) strcpy(res, s); return res; } - 空属性设为NULL:单属性宝可梦的
type2设为NULL,既节省内存,又方便后续判断是否存在第二个属性。 - 及时关闭文件:所有异常退出路径都添加文件关闭操作,避免资源泄漏。
内容的提问来源于stack exchange,提问作者דניאל בוזגלו
相关产品推荐
相关产品推荐

