【问题标题】:how to use both scanf and fgets to read a file如何同时使用 scanf 和 fgets 来读取文件
【发布时间】:2016-08-22 11:28:32
【问题描述】:

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

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 获取第二行。但我不知道该怎么做。

【问题讨论】:

  • 使用 fgets() 然后 strtok() 来拆分字符串。
  • 至于你的当前问题,你得到的崩溃,现在想想。你声明了一个指向char的指针数组,但你从来没有让这些指针真正指向任何地方。
  • 嗨,马特。感谢回复。我是 c 的新手。我不知道如何使用 strtok()。我现在去查一下。
  • 你还有一个问题,那就是你得到的第一行fgets(在你解决了指针问题之后)将是empty。那是因为之前的scanf 没有读取上一行末尾的换行符,所以fgets 将其视为空行。
  • 嗨约阿希姆。谢谢。我以为我在 for 循环中使用了它们。我该如何解决?

标签: c scanf fgets


【解决方案1】:

在函数内部定义的局部变量,除非显式初始化,否则会有一个indeterminate值。对于指针,这意味着它们指向一个看似随机的位置。使用任何未初始化的变量,除了初始化它,都会导致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 |电子| l | l | ○ | \n | +----+----+----+----+----+----+----+----+----+

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

+----+----+----+----+----+----+----+ | \n | h |电子| l | l | ○ | \n | +----+----+----+----+----+----+----+

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

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

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

【讨论】:

  • 谢谢。你让我很容易理解。
【解决方案2】:

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

#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”,所以我们检查为“将返回值与 4 比较”

【讨论】:

  • 检查scanf的返回值是必须
【解决方案3】:

我已经解决了我的问题。我不应该使用 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] = {'\0'};

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 2023-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多