如何同时使用 scanf 和 fgets 读取文件

how to use both scanf and fgets to read a file

我需要阅读以下文本文件:

2 2
Kauri tree
Waterfall
0 0 W S
0 1 E N

我想使用 scanf 获取第一行,使用 fgets 获取第二行和第三行,然后再次使用 scanf 获取其余行.

我写的代码是这样的:

#include <stdio.h>

#define NUM_OF_CHAR 2

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char* str[NUM_OF_CHAR];  //should be char str[NUM_OF_CHAR];

    for (int i = 0; i < node; i++) {
        fgets(str[i], 2, fp);     //should be fgets(str, 2, fp);
    }
    printf("%s", str[0]);         //printf("%s", str);
}

我输入的内容是:

2 2
hello

我得到了Segmentation fault

我在这里看到一个类似的问题,有人提到我可以调用 fgets 一次获取第一行但忽略它然后再次使用 fgets 获取第二行。但是我不知道怎么做。

考虑以下示例,其中注释解释了一些要点:

#include <stdio.h>

#define NUM_OF_CHAR 2
#define LEN_OF_STR 20

int main()
{
    int node, edge;
    FILE* fp;
    fp = stdin;
    char strbuf[LEN_OF_STR];
    // stream is available after that
    // reading numbers
    fscanf(fp, "%d %d", &node, &edge);
    // reading strings
    for (int i = 0; i < node; i++) {
        // reading line from input stream
        fgets(strbuf, LEN_OF_STR, fp);
    }
    // cleaning input buffer
    while (getchar() != '\n');
    // reading lines with data
    char str[NUM_OF_CHAR];
    int a, b;
    for (int i = 0; i < node; i++) {
        // reading two numbers and two characters
        fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]);
        // do something with dada, e.g. output
        printf("%d %d %c %c\n", a, b, str[0], str[1]);
    }
    return 0;
}

当您使用scanffscanf读取数据时,您可以查看结果,例如:

    if (4 == fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]))
    {
        // actions for correct data
    }
    else
    {
        // actions for wrong input
    }

此处格式行有 4 个说明符 - "%d %d %c %c",所以我们检查为 "compare return value with 4"

函数内部定义的局部变量,除非显式初始化,否则具有不确定值。对于指针,这意味着它们指向一个看似随机的位置。使用任何未初始化的变量,除非对其进行初始化,否则会导致 undefined behavior.

这里发生的是 fgets 将使用(未初始化且看似随机的)指针并使用它写入它指向的内存。在大多数情况下,此内存不属于您或您的程序,甚至可能会覆盖其他一些重要数据。这可能会导致崩溃或其他奇怪的行为或结果。

最简单的解决方案是 str 一个字符数组,例如

#define NUM_OF_STRINGS 2
#define STRING_LENGTH 64
...
char str[NUM_OF_STRINGS][STRING_LENGTH];
...
fgets(str[i], sizeof str[i], stdin);

您需要确保上面的 STRING_LENGTH 足以容纳每个字符串 包括 换行符和字符串终止符。在我上面显示的 64 的情况下,这意味着你最多可以有 62 个字符的行。


现在关于我指出的另一个问题,第一次调用 fgets 读取空行。

如果你有输入

2 2
hello

输入存储在内存中的一个缓冲区中,然后scanffgets从这个缓冲区中读取。带有上述输入的缓冲区看起来像这样

+----+----+----+----+----+----+----+----+----+
|  2 |  2 | \n |  h |  e |  l |  l |  o | \n |
+----+----+----+----+----+----+----+----+----+

scanf 调用后读取输入缓冲区的两个数字

+----+----+----+----+----+----+----+
| \n |  h |  e |  l |  l |  o | \n |
+----+----+----+----+----+----+----+

所以循环中对 fgets 的第一次调用将看到换行符。所以它读取换行符然后完成,将字符串 "hello\n" 留在缓冲区中以供 second 调用 fgets.

有几种方法可以解决这个问题。我个人比较喜欢的是通用使用fgets来读取行,如果你需要对行进行简单的解析,那么使用sscanf(注意前导s,也请see here for a good reference of all scanf variants) 这样做。

另一种方法是简单地从输入中读取字符,一次一个字符,然后丢弃它们。当您读取换行符时,停止循环并继续程序的其余部分。

我的问题已经解决了。我不应该使用 char* 指针并使其指向一个数组。传递给 fgets 函数的第一个参数应该是 char*,所以我应该只使用数组。

此外,由于 scanf 已经扫描了第一行,如果我接下来使用 fgets,它会自动获取下一行。

#include <stdio.h>

#define NUM_OF_CHAR 100

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char str[NUM_OF_CHAR] = {'[=10=]'};

    for (int i = 0; i < node; i++) {
        fgets(str, NUM_OF_CHAR, fp);
    }
    printf("%s", str);
}