【问题标题】:Using fscanf() vs. fgets() and sscanf()使用 fscanf() 与 fgets() 和 sscanf()
【发布时间】:2014-04-15 08:59:10
【问题描述】:

在Practical C Programming一书中,我发现fgets()sscanf()的组合用于读取输入。但是,在我看来,仅使用 fscanf() 函数可以更轻松地实现相同的目标:

来自书中(想法,而不是示例):

int main()
{
    int age, weight;
    printf("Enter age and weight: ");

    char line[20];
    fgets(line, sizeof(line), stdin);
    sscanf(line, "%d %d", &age, &weight);

    printf("\nYou entered: %d %d\n", age, weight);
    return 0;
}

我认为应该是这样的:

int main()
{
    int age, weight;
    printf("Enter age and weight: ");
    fscanf(stdin, "%d %d", &age, &weight);

    printf("\nYou entered: %d %d\n", age, weight);
    return 0;
}

还是我遗漏了一些隐藏的怪癖?

【问题讨论】:

  • 你能把书中的代码 sn-p 贴出来让我们看看区别吗?
  • 完成。请参见。这两个程序产生相同的输出(至少在眼睛看来)。

标签: c


【解决方案1】:

fscanf()fgets()/sscanf() 在以下情况下没有区别:

  1. 输入数据格式正确。

发生两种类型的错误:I/O 和格式。 fscanf() 在一个函数中同时处理这两种错误类型,但提供的恢复选项很少。单独的 fgets()sscanf() 允许将 I/O 问题与格式问题进行逻辑分离,从而更好地恢复。

  1. 只有 1 个解析路径为 fscanf()

fgets/sscanf 一样,将 I/O 与扫描分开允许多个sscanf() 选项。如果给定的缓冲区扫描无法实现所需的结果,则可以使用其他不同格式的sscanf()

  1. 没有嵌入'\0'

'\0' 很少出现,但如果出现,sscanf() 不会将其视为扫描停止,而fscanf() 会继续。

在所有情况下,检查所有三个函数的结果。

【讨论】:

    【解决方案2】:

    仅使用fscanf() 的问题主要在于错误管理。

    假设您在两个程序中都输入了“51 岁,85 公斤”。

    第一个程序在sscanf() 中失败,您仍然有line 向用户报告错误,尝试不同的解析替代方法,something;

    第二个程序在 失败,age 可用,weight 不可用。

    请记住始终检查 *scanf() 的返回值以进行错误检查。

        fgets(line, sizeof(line), stdin);
        if (sscanf(line, "%d%d", &age, &weight) != 2) /* error with input */;
    

    编辑

    使用您的第一个程序,错误后,输入缓冲区被清除;在第二个程序中,输入缓冲区以 YEAR 开头...

    第一种情况下的恢复很容易;第二种情况下的恢复必须通过某种方式清除输入缓冲区。

    【讨论】:

      【解决方案3】:

      这两种方法存在一些行为差异。如果您使用fgets() + sscanf(),则必须在同一行输入两个值,而fscanf() on stdin(或等效地,scanf())如果找不到您输入的第一行的第二个值。

      但是,最重要的区别可能与处理错误情况以及面向行的输入和面向场的输入的混合有关。

      如果您在使用fgets() 阅读后阅读了无法用sscanf() 解析的行,您的程序可以简单地丢弃该行并继续前进。但是,fscanf() 在转换字段失败时会将所有输入留在流中。所以,如果你没有读取你想要的输入,你就必须去读取所有你想忽略的数据。

      如果你想在代码中混合面向字段(ala scanf())和面向行(例如fgets())调用,另一个微妙的问题就出现了。例如,当scanf() 转换int 时,它会在输入流上留下\n(假设有一个,例如按下回车键),这将导致随后对fgets() 的调用返回立即在输入中仅包含该字符。对于新程序员来说,这是一个非常普遍的问题。

      因此,尽管您可以像这样使用 fscanf() 是对的,但使用 fgets() + sscanf() 可能会避免一些麻烦。

      【讨论】:

      • 这是真的。另一方面,您会增加一些麻烦,因为fgets 会读取具有最大长度的行,这可能会导致麻烦。想象一下,您的缓冲区是 10 个字符(例如,被夸大了),然后给定输入:" 12345\n",您将收到 " 12""345\n"。最安全的方法是在 fgetsrealloc 上使用循环来读取完整的行,无论其大小。
      • @Shahbaz 我还是 C 的新手,但我尝试了这个,发现“12345”并没有像你解释的那样被拆分(“12”和“345\n”)。相反,我在第一个变量中得到“12345”,在第二个变量中得到一些负垃圾值。你想表达什么观点?为什么会发生这种情况?
      • Shahbaz:对于简单的问题,选择一个足够大的缓冲区,读取第一个字符并在循环中忽略其他字符,直到您读取 '\n' :)
      • @dotslash,使用fgets,您必须提供有界缓冲区。如果在该大小的边界上有一个数字,它可以被拆分。在您自己的示例中,尝试输入 17 个空格,后跟 6 位数字。
      • @chux,如果一行开始变得异常大,您肯定会产生故障。我的意思是,如果您想保持最大大小,这很好,那么您必须确保您读取的行适合缓冲区,否则数据可能不可靠,您可能需要忽略你已经读过的那行的哪一部分以及还剩下哪一部分。顺便说一句,通过耗尽内存来使程序崩溃并不是真正的攻击。你可以limit its memory,或者发送一个 KILL 信号或其他任何东西!这不是安全问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多