【问题标题】:Won't read from file to struct不会从文件读取到结构
【发布时间】:2017-04-13 21:41:06
【问题描述】:

这个问题我已经坐了 2 天了,我不知道我做错了什么。我试过调试(有点?还是新的),点击这个链接:https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ 我试过谷歌和各种各样的东西。基本上我是从这种格式的文件中读取的:

R1 Fre 17/07/2015 18.00 FCN - SDR 0 - 2 3.211

我必须让程序将它读入一个结构,但是当我尝试打印信息时,它完全错了。我的代码如下所示:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_INPUT 198

typedef struct game{
    char   weekday[4],
           home_team[4],
           away_team[4];
    int    round,
           hour,
           minute,
           day,
           month,
           year,
           home_goals,
           away_goals,
           spectators;}game;

game make_game(FILE *superliga);

int main(void){
    int    input_number,
           number_of_games = 198,
           i = 0;
    game   tied[MAX_INPUT];

    FILE *superliga;
    superliga = fopen("superliga-2015-2016.txt", "r");

    for(i = 0; i < number_of_games; ++i){
                tied[i] = make_game(superliga);
                printf("R%d %s %d/%d/%d %d.%d %s - %s %d - %d %d\n",
                        tied[i].round, tied[i].weekday, tied[i].day, tied[i].month,
                        tied[i].year, tied[i].hour, tied[i].minute, tied[i].home_team,
                        tied[i].away_team, tied[i].home_goals, tied[i].away_goals,
                        tied[i].spectators);}

 fclose(superliga);

 return 0;
}

game make_game(FILE *superliga){
    double spect;
    struct game game_info;

    fscanf(superliga, "R%d %s %d/%d/%d %d.%d %s - %s %d - %d %lf\n",
            &game_info.round, game_info.weekday, &game_info.day, &game_info.month,
            &game_info.year, &game_info.hour, &game_info.minute, game_info.home_team,
            game_info.away_team, &game_info.home_goals, &game_info.away_goals,
            &spect);

         game_info.spectators = spect * 1000;

    return game_info;
 }

【问题讨论】:

  • 我认为\n 可能会导致fscanf 出现问题
  • 使用调试器逐行执行代码。并检查 fscanf 返回的内容。
  • 用 C 语言解析文本文件数据很糟糕。如果有帮助,我觉得你必须这样做。知道当你到达那里时,其他语言会为你提供更好的工具。
  • 出了什么问题?你得到的输出是什么?
  • Elyasin,很多行看起来像这样:R19528456 X a 1952502352/6422476/0 1952598979.271333979 EKbt? - ? 93 - 6412588 -2147483648

标签: c file struct


【解决方案1】:

如果文件中的每一行都是单独的记录,则应将每一行读取为一个字符串,然后尝试解析每个字符串。

(请注意,这还具有推测解析的附加功能:您可以尝试以几种不同的格式解析行,并接受正确解析的那个。我喜欢在接受时使用它比如向量输入,这样用户就可以根据自己的喜好使用x y zx, y, zx/y/z(x,y,z)[x,y,z]&lt;x y z&gt;&lt;x,y,z&gt;等等。毕竟,每种格式多出一个 scanf。)

要读取行,您可以在本地缓冲区中使用fgets()。本地缓冲区必须足够长。如果程序仅在 POSIX.1 机器上运行(即不在 Windows 上),那么您可以改用 getline(),它可以根据需要动态重新分配给定的缓冲区,因此您不受任何特定行长度的限制.

要解析字符串,请使用sscanf()

请注意,所有 scanf 系列函数的模式中的所有制表符、空格和换行符的处理方式完全相同:它们表示任意数量的任意类型的空白。换句话说,\n 并不意味着“然后是换行符”;它与空格的含义相同,即“这里可能还有一些空格”。但是,除了%c%[ 之外的所有转换都会自动跳过任何前导空格;因此,除了这两者之一之前的空格外,模式中的空格仅对我们人类有意义,它们在扫描中没有任何功能影响。

所有 scanf 系列函数都返回成功转换的次数。 (唯一的例外是“转换”%n,它产生消耗的字符数;一些实现将其包含在转换计数中,而另一些则不包含。)如果输入结束发生在第一次转换之前,或者发生读取错误,或者输入与模式的固定部分不匹配,函数将返回EOF

即使您禁止保存转换结果——例如,如果输入中有一个不需要的单词,您可以转换但使用%*s 将其丢弃——它也会被计算在内。因此,例如,sscanf(line, " %*d %*s %*d") 返回 3,如果该行以整数开头,后跟一个单词(任何不是换行符也不包含空格的单词),然后是一个整数。

而不是让函数返回解析的结构,传递指向结构的指针(以及要读取的文件句柄),然后返回状态码。我更喜欢 0 代表成功,非零代表失败,但可以随意更改。

换句话说,我建议您将读取功能更改为

#ifndef  GAME_LINE_MAX
#define  GAME_LINE_MAX   1022
#endif

int read_game(game *one, FILE *in)
{
    char  buffer[GAME_LINE_MAX + 2]; /* + '\n' + '\0' */
    char *line;

    /* Sanity check: no NULL pointers accepted! */
    if (!one || !in)
        return -1;

    /* Paranoid check: Fail if read error has already occurred. */
    if (ferror(in))
        return -1;

    /* Read the line */
    line = fgets(buffer, sizeof buffer, in);
    if (!line)
        return -1;

    /* Parse the game; pattern from OP's example: */
    if (sscanf(line, "R%d %3s %d/%d/%d %d.%d %3s - %3s %d - %d %d\n",
                     &(one->round), one->weekday,
                     &(one->day), &(one->month), &(one->year),
                     &(one->hour), &(one->minute)
                     one->home_team,
                     one->away_team,
                     &(one->home_goals),
                     &(one->away_goals),
                     &(one->spectators)) < 12)
        return -1; /* Line not formatted like above */

    /* Spectators in the file are in units of 1000; convert: */
    one->spectators *= 1000;

    /* Success. */
    return 0;
}

要循环使用上述函数,从标准输入(stdin)一个接一个地读取游戏:

game  g;

while (!read_game(&g, stdin)) {

    /* Do something with current game stats, g */

}

if (ferror(stdin)) {

    /* Read error occurred! */

} else
if (!feof(stdin)) {

    /* Not all data was read/parsed! */

}

上面的两个if 子句用于检查是否存在真正的读取错误(如硬件问题或类似问题),以及是否存在未读取/未解析的数据(不在文件末尾) ,分别。

扫描模式与OP相比有两个不同:首先,所有解析的字符串限制为3个字符,因为该结构每个只有3+1的空间。一个字符是为字符串'\0' 的结尾保留的,不计入%s 的最大长度。其次,我直接解析观众人数,如果成功则将场数乘以 1000。

还要注意我是如何使用one-&gt;weekdayone-&gt;home_teamone-&gt;away_team 来引用字符数组的。这是可行的,因为可以将数组变量用作指向该数组中第一个元素的指针。 (给定char a[5];a&amp;a&amp;(a[0]) 都可以用来引用数组a 中的第一个元素)。我喜欢在扫描时使用这种“原始形式”,因为这样更容易将它们与%s 转换匹配,并确保模式与参数匹配。

【讨论】:

  • 感谢您的回答!由于一些错误,我对其进行了更改并将“*one”更改为“*g”。不幸的是,我现在遇到一个我不知道如何处理的错误:错误:从类型'int'分配给类型'game {aka struct game}'时不兼容的类型:tied [i] = read_game(g,在);
  • @NikolajAB:谢谢;我更正了函数示例。我在代码 sn -p 前加了一两段作为解释,以及一个使用示例。基本上,您需要传递一个指向它的指针,而不是返回一个game 结构;如果成功,该函数将返回0,如果失败则返回非零值。换句话说,使用if (read_game(&amp;(tied[i]), in)) { /* Could not read game! */ } else { /* Game read successfully */ }
【解决方案2】:

问题出在您的文件中。它以空格开头,而不是您在控制字符串中所述的R's。

检查fscanf()的返回值,你会发现它每次都是零。

如果您在 fscanf() 调用中添加前导空格,您的问题将得到解决,如下所示:

fscanf(superliga, " R%d %s %d/%d/%d %d.%d %s - %s %d - %d %lf\n",
            &game_info.round, game_info.weekday, &game_info.day, &game_info.month,
            &game_info.year, &game_info.hour, &game_info.minute, game_info.home_team,
            game_info.away_team, &game_info.home_goals, &game_info.away_goals,
            &spect);

【讨论】:

  • 我尝试添加一个空格,却得到了同样奇怪的输出。
  • 你确定吗?在这里,您的程序在进行一次更改后可以完美运行。
  • 该死,刚刚尝试将我的代码从这里复制到一个新文件中,而不是编辑我的旧文件,它成功了!太感谢了!这么小的一件事情会引起这么多的悲伤,真是令人沮丧!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
  • 1970-01-01
  • 1970-01-01
  • 2015-04-06
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多