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

文本文件存入结构体数组遇单类型宝可梦解析错误求助

问题分析与解决

问题现象

尝试将宝可梦文本数据存入结构体数组,文本示例:

#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);
}

内存分配优化建议

  1. 避免重复调用countPok():原代码多次调用该函数,会重复打开/关闭文件,建议先调用一次存到变量中,减少IO开销。
  2. 完善内存清理逻辑:原代码内存分配失败时,仅释放当前字段的历史内存(比如name分配失败时未释放number),修复后的代码会清理所有已分配资源,避免内存泄漏。
  3. 用strdup()简化代码:strdup()自动分配足够内存并复制字符串,比手动malloc+strcpy更简洁;若环境不支持该函数,可自行实现:
    char* strdup(const char* s) {
        char* res = malloc(strlen(s)+1);
        if(res) strcpy(res, s);
        return res;
    }
    
  4. 空属性设为NULL:单属性宝可梦的type2设为NULL,既节省内存,又方便后续判断是否存在第二个属性。
  5. 及时关闭文件:所有异常退出路径都添加文件关闭操作,避免资源泄漏。

内容的提问来源于stack exchange,提问作者דניאל בוזגלו

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 17:35:16