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

如何从文件读取数据并填充C语言的user与following结构体?

如何从特定格式文件中读取数据填充嵌套的C语言结构体

我定义了两个C语言结构体,其中following是链表节点:

typedef struct following{
    char nick[6];
    int last_message;
    bool first_time;
    struct following *next;
}following;

typedef struct user{
    char nick[6];
    char name[26];
    int n_messages;
    int n_following;
    int n_followers;
    following *arr_following;
    following *arr_unfollowed;
}user;

需要从如下格式的文件中读取数据填充user结构体:

zsq4r Pseu Donym 3 1 2;zero7 2 true! zero7 James Bond 4 3 3;zsq4r 3 true!zero7 4 false!MrPym 1 true! MrPym A Perfect Spy 1 3 1;zsq4r 3 true!zero7 4 true!AlecS 1 true! AlecS He Who Came from the Cold 1 0 1;

文件规则:

  • 以;分隔的内容用于填充user结构体(每个分号块对应一个完整用户)
  • 以!分隔的内容中,最后一个!之后的部分是用户基本信息,之前的每个!分隔块是following结构体的数据
  • 每个用户块的第二个元素是用户名称,最长25字符且可包含空格,例如"He Who Came from the Cold"是有效名称

我尝试了如下代码,但只能填充user结构体的部分内容,无法同时处理following链表:

void read_from_file(hashtable *active_users, FILE *fp_active){
    const char *delimiter1 = "!";
    const char *delimiter2 = ";";
    char *last_token;
    char buffer[1540];
    while(fgets(buffer, 1540, fp_active)) {
        user *new_user = malloc(sizeof(user));
        last_token = strtok( buffer, delimiter2);
        while( last_token != NULL ){
            sscanf(last_token,"%s %[^\n] %d %d %d", new_user->nick, new_user->name, &new_user->n_messages, &new_user->n_followers);
            last_token = strtok( NULL, delimiter1);
        }
        insert(active_users, new_user);
    }
}

虽然last_token能正确获取字符串片段,但无法同时填充两个结构体,sscanf仅能填充user结构体的部分内容,寻求解决方法。


解决方案

你的核心问题是没区分开用户块里的关注数据和用户自身信息,需要分层拆分字符串:先拆出每个用户块,再从用户块里分离关注列表和用户基本数据,最后分别填充结构体并构建链表。下面是具体实现思路和修正后的代码:

步骤拆解

  1. 拆分用户块:用strtok按;分割整个文件内容,每个分割结果对应一个完整用户的数据集。
  2. 拆分用户内部数据:对每个用户块,按!分割出多个子串,最后一个子串是用户基本信息,前面的都是following节点数据。
  3. 构建关注链表:对每个关注子串,用sscanf读取昵称、消息数和first_time标记,创建链表节点并添加到用户的arr_following链表中。
  4. 填充用户结构体:处理最后一个子串,读取用户昵称、带空格的名称以及各项统计数字,注意避免字符串越界和格式匹配错误。

完整修正代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// 结构体定义(保留以保证代码完整性)
typedef struct following{
    char nick[6];
    int last_message;
    bool first_time;
    struct following *next;
}following;

typedef struct user{
    char nick[6];
    char name[26];
    int n_messages;
    int n_following;
    int n_followers;
    following *arr_following;
    following *arr_unfollowed;
}user;

// 辅助函数:创建新的following节点
following* create_following_node(const char *nick, int last_msg, bool first_time) {
    following *node = malloc(sizeof(following));
    if (!node) return NULL;
    strncpy(node->nick, nick, 5); // 限制长度,避免溢出
    node->nick[5] = '\0';
    node->last_message = last_msg;
    node->first_time = first_time;
    node->next = NULL;
    return node;
}

// 辅助函数:将节点添加到链表尾部
void add_following_node(following **head, following *new_node) {
    if (!*head) {
        *head = new_node;
        return;
    }
    following *curr = *head;
    while (curr->next) curr = curr->next;
    curr->next = new_node;
}

void read_from_file(hashtable *active_users, FILE *fp_active){
    const char *user_delimiter = ";";
    const char *following_delimiter = "!";
    char buffer[1540];
    
    while(fgets(buffer, sizeof(buffer), fp_active)) {
        // 去除缓冲区末尾的换行符(如果存在)
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n') buffer[len-1] = '\0';

        // 拆分每个用户块
        char *user_block = strtok(buffer, user_delimiter);
        while (user_block != NULL) {
            // 跳过空块(比如连续;;或末尾的空内容)
            if (strlen(user_block) == 0) {
                user_block = strtok(NULL, user_delimiter);
                continue;
            }

            // 初始化新用户结构体
            user *new_user = malloc(sizeof(user));
            if (!new_user) {
                perror("Failed to allocate memory for user");
                user_block = strtok(NULL, user_delimiter);
                continue;
            }
            memset(new_user, 0, sizeof(user)); // 初始化所有成员为0/NULL

            // 复制用户块(避免strtok修改原字符串影响后续操作)
            char *block_copy = strdup(user_block);
            if (!block_copy) {
                perror("Failed to duplicate user block");
                free(new_user);
                user_block = strtok(NULL, user_delimiter);
                continue;
            }

            char *token = strtok(block_copy, following_delimiter);
            while (token != NULL) {
                char test_nick[6];
                int test_num;
                char test_bool[6];
                // 尝试匹配following的格式:昵称 + 消息数 + true/false
                if (sscanf(token, "%5s %d %5s", test_nick, &test_num, test_bool) == 3) {
                    bool first_time = (strcmp(test_bool, "true") == 0);
                    following *node = create_following_node(test_nick, test_num, first_time);
                    if (node) add_following_node(&new_user->arr_following, node);
                } else {
                    // 匹配用户信息格式:昵称 + 带空格的名称 + 三个数字
                    char name_buf[26];
                    if (sscanf(token, "%5s %25[^\d] %d %d %d", 
                               new_user->nick, name_buf, 
                               &new_user->n_messages, &new_user->n_following, &new_user->n_followers) == 5) {
                        // 去除名称末尾的多余空格
                        char *trimmed_end = strrchr(name_buf, ' ');
                        if (trimmed_end) *trimmed_end = '\0';
                        strncpy(new_user->name, name_buf, 25);
                        new_user->name[25] = '\0';
                    }
                }
                token = strtok(NULL, following_delimiter);
            }

            free(block_copy);
            insert(active_users, new_user); // 将用户插入哈希表
            user_block = strtok(NULL, user_delimiter);
        }
    }
}

关键注意事项

  • 避免字符串溢出:所有字符数组操作都用strncpy并手动添加结束符,严格限制长度(比如nick最多5个字符+结束符)。
  • strtok的副作用:strtok会修改原字符串,所以处理用户块时必须先复制一份,防止破坏后续的分割逻辑。
  • 内存安全:分配内存后要检查是否成功,出错时及时释放已分配的内存,避免内存泄漏。
  • 格式容错:通过sscanf的返回值判断是否匹配成功,防止因文件格式异常导致结构体成员乱码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:53:50