如何从文件读取数据并填充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结构体的部分内容,寻求解决方法。
解决方案
你的核心问题是没区分开用户块里的关注数据和用户自身信息,需要分层拆分字符串:先拆出每个用户块,再从用户块里分离关注列表和用户基本数据,最后分别填充结构体并构建链表。下面是具体实现思路和修正后的代码:
步骤拆解
- 拆分用户块:用
strtok按;分割整个文件内容,每个分割结果对应一个完整用户的数据集。 - 拆分用户内部数据:对每个用户块,按
!分割出多个子串,最后一个子串是用户基本信息,前面的都是following节点数据。 - 构建关注链表:对每个关注子串,用
sscanf读取昵称、消息数和first_time标记,创建链表节点并添加到用户的arr_following链表中。 - 填充用户结构体:处理最后一个子串,读取用户昵称、带空格的名称以及各项统计数字,注意避免字符串越界和格式匹配错误。
完整修正代码
#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
相关产品推荐
相关产品推荐

