不会从文件读取到struct。

时间:2023-02-06 11:00:32

I've been sitting with this problem for 2 days and I can't figure out what I'm doing wrong. I've tried debugging (kind of? Still kind of new), followed this link: https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ And I've tried Google and all kinds of things. Basically I'm reading from a file with this format:

我已经为这个问题坐了两天了,我不知道我做错了什么。我试过调试(某种程度上?仍然有点新鲜),跟随这个链接:https://ericlippert.com/2014/03/05/how-to-debug-small programs/我尝试了谷歌和各种各样的东西。基本上我是从一个文件中读取的,格式如下:

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

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

and I have to make the program read this into a struct, but when I try printing the information it comes out all wrong. My code looks like this:

我必须让程序把它读成一个结构体,但是当我尝试打印信息时,它就会出错。我的代码是这样的:

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

2 个解决方案

#1


0  

The problem is in your file. It starts with whitespaces, not with R's as you stated in the control string.

问题在你的档案里。它以白空格开头,而不是像您在控制字符串中所说的那样以R开头。

Check the return value of fscanf() and you'll see that it's zero everytime.

检查fscanf()的返回值,您会发现每次都是0。

If you add a leading whitespace to your fscanf() call, your problem will be solved, like this:

如果在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);

#2


0  

If each line in the file is a separate record, you should read each line as a string, then try to parse each string.

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

(Note that this also has the added feature of speculative parsing: you can try parsing the line in several different formats, and accept the one that parses correctly. I like to use this when I accept e.g. vector inputs, so that the user can use x y z, x, y, z, x/y/z, (x,y,z), [x,y,z], <x y z>, <x,y,z>, and so on, depending on what they like. It's only one additional scanf per format, after all.)

(注意,这也增加了投机性解析的特性:您可以尝试以几种不同的格式解析行,并接受正确解析的行。当我接受向量输入时,我喜欢用这个,这样用户就可以使用x y z x y y z z z z z z z z x/y z, (x,y,z) [x,y,z], ,等等,这取决于他们喜欢什么。毕竟,每种格式只有一个额外的scanf。 ,y,z>

To read lines, you can use fgets() into a local buffer. The local buffer must be long enough. If the program is to run on POSIX.1 machines only (i.e., not on Windows), then you can use getline() instead, which can dynamically reallocate the given buffer as needed, so you're not limited to any specific line length.

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

To parse the string, use sscanf().

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

Note that all tabs, spaces, and newlines in the pattern in all of the scanf family of functions are treated exactly the same: they indicate any number of any type of whitespace. In other words, \n does not mean "and then a newline"; it means the same as a space, i.e. "and possibly some whitespace here". However, all conversions except %c and %[ automatically skip any leading whitespace; so, with the exception of a space before one of those two, the spaces in the pattern are only meaningful to us humans, they do not have any functional effect in the scanning.

请注意,在所有的scanf系列函数中,所有的制表符、空格和换行符都是完全相同的:它们表示任何类型的空格。换句话说,\n不是“然后换行”的意思;它的意思和空间一样,也就是。“可能还有一些空白”。但是,除了%c和%[自动跳过任何主要空格;所以,除了这两个空间中的一个之外,模式中的空间只对我们人类有意义,它们在扫描中没有任何功能作用。

All scanf family of functions return the number of successful conversions. (The only exception is the "conversion" %n, which yields the number of characters consumed; some implementations include it in the conversion count, and some others do not.) If end of input occurs prior to the first conversion, or a read error occurs, or the input does not match with the fixed part of the pattern, the functions will return EOF.

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

Even if you suppress saving the result of a conversion -- for example, if you have a word in the input you don't need, you can convert but discard it with %*s --, it is counted. So, for example sscanf(line, " %*d %*s %*d") returns 3 if the line starts with an integer, followed by a word (anything that is not a newline nor contains whitespace), followed by an integer.

即使你抑制了转换的结果——例如,如果你在输入中有一个词,你不需要,你可以转换,但是用%*s放弃它——它被计算了。因此,例如,sscanf(行,“%*d %*s %*d”)返回3,如果该行以整数开始,后面跟着一个单词(任何不是换行符,也不包含空格),后跟一个整数。

Rather than have the function return the parsed structure, pass a pointer to the structure (and the file handle to read from), and return a status code. I prefer 0 for success, and nonzero for failure, but feel free to change that.

与其让函数返回解析后的结构,不如传递一个指向结构(以及要读取的文件句柄)的指针,并返回状态代码。我喜欢0代表成功,而不喜欢0代表失败,但请随意改变。

In other words, I'd suggest you change your read function into

换句话说,我建议您将read函数改为

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

To use the above function in a loop, reading games one after another from standard input (stdin):

要在循环中使用上述功能,请从标准输入(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! */

}

The two if clauses above are to check if there was a real read error (as in, a problem with the hardware or something like that), and whether there was unread/unparsed data (not at end of file), respectively.

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

There are two differences in the scanning pattern compared to the OP: First, all strings parsed are limited to 3 characters, because the structure has only room for 3+1 each. The one character is reserved for the end of string '\0', which is not counted in the maximum length for %s. Second, I parse the spectator count directly, and just multiply the field by 1000 if successful.

与OP相比,扫描模式有两个不同之处:首先,解析的所有字符串都被限制为3个字符,因为该结构只有3+1个字符的空间。一个字符被保留为字符串'\0'的末尾,它不被计算为%s的最大长度。第二,我直接解析观察者的计数,如果成功的话,将字段乘以1000。

Also note how I used one->weekday, one->home_team, and one->away_team to refer to the character arrays. This works, because an array variable can be used as if it was a pointer to the first element in that array. (Given char a[5];, a and &a and &(a[0]) can all be used to refer to the first element in the array a). I like to use this "raw form" when scanning, because it makes it easier to match them to %s conversions, and ensure the pattern matches the parameters.

还要注意我如何使用一个->工作日、一个->home_team和一个->away_team来引用字符数组。这是可行的,因为可以将数组变量用作数组中第一个元素的指针。(给定一个[5];,a和&(一个[0])都可以用于引用数组a中的第一个元素)。我喜欢在扫描时使用这个“原始表单”,因为它使它们更容易匹配到%s转换,并确保模式匹配参数。

#1


0  

The problem is in your file. It starts with whitespaces, not with R's as you stated in the control string.

问题在你的档案里。它以白空格开头,而不是像您在控制字符串中所说的那样以R开头。

Check the return value of fscanf() and you'll see that it's zero everytime.

检查fscanf()的返回值,您会发现每次都是0。

If you add a leading whitespace to your fscanf() call, your problem will be solved, like this:

如果在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);

#2


0  

If each line in the file is a separate record, you should read each line as a string, then try to parse each string.

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

(Note that this also has the added feature of speculative parsing: you can try parsing the line in several different formats, and accept the one that parses correctly. I like to use this when I accept e.g. vector inputs, so that the user can use x y z, x, y, z, x/y/z, (x,y,z), [x,y,z], <x y z>, <x,y,z>, and so on, depending on what they like. It's only one additional scanf per format, after all.)

(注意,这也增加了投机性解析的特性:您可以尝试以几种不同的格式解析行,并接受正确解析的行。当我接受向量输入时,我喜欢用这个,这样用户就可以使用x y z x y y z z z z z z z z x/y z, (x,y,z) [x,y,z], ,等等,这取决于他们喜欢什么。毕竟,每种格式只有一个额外的scanf。 ,y,z>

To read lines, you can use fgets() into a local buffer. The local buffer must be long enough. If the program is to run on POSIX.1 machines only (i.e., not on Windows), then you can use getline() instead, which can dynamically reallocate the given buffer as needed, so you're not limited to any specific line length.

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

To parse the string, use sscanf().

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

Note that all tabs, spaces, and newlines in the pattern in all of the scanf family of functions are treated exactly the same: they indicate any number of any type of whitespace. In other words, \n does not mean "and then a newline"; it means the same as a space, i.e. "and possibly some whitespace here". However, all conversions except %c and %[ automatically skip any leading whitespace; so, with the exception of a space before one of those two, the spaces in the pattern are only meaningful to us humans, they do not have any functional effect in the scanning.

请注意,在所有的scanf系列函数中,所有的制表符、空格和换行符都是完全相同的:它们表示任何类型的空格。换句话说,\n不是“然后换行”的意思;它的意思和空间一样,也就是。“可能还有一些空白”。但是,除了%c和%[自动跳过任何主要空格;所以,除了这两个空间中的一个之外,模式中的空间只对我们人类有意义,它们在扫描中没有任何功能作用。

All scanf family of functions return the number of successful conversions. (The only exception is the "conversion" %n, which yields the number of characters consumed; some implementations include it in the conversion count, and some others do not.) If end of input occurs prior to the first conversion, or a read error occurs, or the input does not match with the fixed part of the pattern, the functions will return EOF.

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

Even if you suppress saving the result of a conversion -- for example, if you have a word in the input you don't need, you can convert but discard it with %*s --, it is counted. So, for example sscanf(line, " %*d %*s %*d") returns 3 if the line starts with an integer, followed by a word (anything that is not a newline nor contains whitespace), followed by an integer.

即使你抑制了转换的结果——例如,如果你在输入中有一个词,你不需要,你可以转换,但是用%*s放弃它——它被计算了。因此,例如,sscanf(行,“%*d %*s %*d”)返回3,如果该行以整数开始,后面跟着一个单词(任何不是换行符,也不包含空格),后跟一个整数。

Rather than have the function return the parsed structure, pass a pointer to the structure (and the file handle to read from), and return a status code. I prefer 0 for success, and nonzero for failure, but feel free to change that.

与其让函数返回解析后的结构,不如传递一个指向结构(以及要读取的文件句柄)的指针,并返回状态代码。我喜欢0代表成功,而不喜欢0代表失败,但请随意改变。

In other words, I'd suggest you change your read function into

换句话说,我建议您将read函数改为

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

To use the above function in a loop, reading games one after another from standard input (stdin):

要在循环中使用上述功能,请从标准输入(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! */

}

The two if clauses above are to check if there was a real read error (as in, a problem with the hardware or something like that), and whether there was unread/unparsed data (not at end of file), respectively.

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

There are two differences in the scanning pattern compared to the OP: First, all strings parsed are limited to 3 characters, because the structure has only room for 3+1 each. The one character is reserved for the end of string '\0', which is not counted in the maximum length for %s. Second, I parse the spectator count directly, and just multiply the field by 1000 if successful.

与OP相比,扫描模式有两个不同之处:首先,解析的所有字符串都被限制为3个字符,因为该结构只有3+1个字符的空间。一个字符被保留为字符串'\0'的末尾,它不被计算为%s的最大长度。第二,我直接解析观察者的计数,如果成功的话,将字段乘以1000。

Also note how I used one->weekday, one->home_team, and one->away_team to refer to the character arrays. This works, because an array variable can be used as if it was a pointer to the first element in that array. (Given char a[5];, a and &a and &(a[0]) can all be used to refer to the first element in the array a). I like to use this "raw form" when scanning, because it makes it easier to match them to %s conversions, and ensure the pattern matches the parameters.

还要注意我如何使用一个->工作日、一个->home_team和一个->away_team来引用字符数组。这是可行的,因为可以将数组变量用作数组中第一个元素的指针。(给定一个[5];,a和&(一个[0])都可以用于引用数组a中的第一个元素)。我喜欢在扫描时使用这个“原始表单”,因为它使它们更容易匹配到%s转换,并确保模式匹配参数。