如何正确使用fscanf读取含空格与'-'的足球比赛数据文件
解决C语言解析足球比赛TXT数据的问题
首先,你的核心问题是fscanf格式字符串没有正确匹配文本中的固定分隔符-,另外file_read里用feof循环也有坑,咱们一步步来解决:
一、修复fscanf的格式问题
你的数据里,队名之间是teamA - teamB,比分是goalsA - goalsB,这些-是固定的分隔符,直接把它们写进fscanf的格式字符串里就行——fscanf会自动跳过前后的空白字符。
修改read_match函数的fscanf语句:
Match read_match(FILE *file_ptr) { Match input = {0}; // 初始化结构体,避免垃圾值干扰调试 // 直接把固定的"-"写进格式串,精准匹配数据中的分隔规则 int read_count = fscanf(file_ptr, "%s %s %s %s - %s %d - %d %d", input.day, input.date, input.time, input.team_A, input.team_B, &input.goals_A, &input.goals_B, &input.spectators); // 调试时可以打印读取成功的字段数,快速定位问题 if (read_count != 8) { printf("Warning: 读取一行失败,仅读取了%d个字段\n", read_count); } printf("(day) %s (date) %s (time) %s (teamA) %s (teamB) %s (scoreA) %d (scoreB) %d (spect) %d\n", input.day, input.date, input.time, input.team_A, input.team_B, input.goals_A, input.goals_B, input.spectators); return input; }
二、修复file_read的循环逻辑
原代码用while(!feof(file_ptr))是典型的错误写法——feof只有在读取失败后才会被置位,这会导致你最后多读一行无效数据。正确的做法是通过fscanf的返回值判断是否成功读取了完整的一行:
修改file_read函数:
int file_read(char *file_input, Match arr_matches[]) { int r = 0; FILE *file_ptr = fopen(file_input, "r"); if (file_ptr == NULL) { printf("Error! Can't open file!"); return -1; // 返回-1表示打开错误,和正常读取行数区分开 } Match temp; // 用fscanf返回值判断是否成功读取8个字段 while (fscanf(file_ptr, "%s %s %s %s - %s %d - %d %d", temp.day, temp.date, temp.time, temp.team_A, temp.team_B, &temp.goals_A, &temp.goals_B, &temp.spectators) == 8) { arr_matches[r] = temp; r++; // 防止数组越界,避免内存错误 if (r >= MATCHES) { printf("Warning: 比赛数量超过数组上限,已停止读取\n"); break; } } fclose(file_ptr); return r; }
三、用strtok的替代方案
如果你想用strtok来解析,需要注意分隔符是空格和-,而且要跳过空token(因为-前后有空格,会产生空的分割结果)。修改后的strtok示例:
#include <string.h> #include <stdio.h> #include <stdlib.h> int main () { char str[80] = "Fre 12/07 19.00 FCM - EFB 1 - 0 7310 "; const char *delim = " -"; // 分隔符是空格和- char *token; token = strtok(str, delim); int field_idx = 0; while( token != NULL ) { // 跳过空token(连续分隔符会产生空结果) if (strlen(token) == 0) { token = strtok(NULL, delim); continue; } // 根据字段索引对应到结构体的各个字段 switch(field_idx) { case 0: printf("Day: %s\n", token); break; case 1: printf("Date: %s\n", token); break; case 2: printf("Time: %s\n", token); break; case 3: printf("Team A: %s\n", token); break; case 4: printf("Team B: %s\n", token); break; case 5: printf("Goals A: %d\n", atoi(token)); break; case 6: printf("Goals B: %d\n", atoi(token)); break; case 7: printf("Spectators: %d\n", atoi(token)); break; } field_idx++; token = strtok(NULL, delim); } return 0; }
这种方法适合数据格式可能有变动的场景,但需要手动处理字符串到整数的类型转换。
四、额外注意事项
- 结构体初始化:一定要初始化结构体(比如
Match input = {0};),避免未初始化的垃圾值干扰调试结果。 - 数组越界保护:
file_read里要检查r是否超过MATCHES,防止数组溢出导致内存错误。 - 错误处理区分:
fopen失败返回-1,而不是1,这样main函数可以区分是打开错误还是正常读取了1行数据。
内容的提问来源于stack exchange,提问作者MMAndersen




