使用fscanf()与fgets()和sscanf()的区别

11

在《实用C编程》这本书中,我发现使用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;
}

或者我是漏掉了某些隐藏的怪癖吗?


1
你能否发布书中的代码片段,以便我们可以看到差异吗? - Engineer2021
这两个程序产生完全相同的输出(至少在视觉上)。 - ankush981
3个回答

18
两种方法有一些行为差异。如果你使用fgets() + sscanf(),那么必须在同一行上输入两个值,而fscanf()stdin(或等效地scanf())上读取它们时,如果在你输入的第一行中找不到第二个值,它会从不同的行上读取这些值。
但是,最重要的区别可能与处理错误情况以及混合面向行的输入和面向字段的输入有关。
如果使用sscanf()无法解析的行,可以用fgets()读取后直接丢弃该行并继续执行程序。然而,当fscanf()转换失败时,会将所有输入留在流中。因此,如果未能读取所需的输入,则必须手动读取要忽略的所有数据。
另一个微妙的问题出现在你希望在代码中混合面向字段(如scanf())和面向行(如fgets())调用时。例如,当scanf()转换一个int时,它将在输入流中留下一个\n(假设有一个,比如按下回车键),这将导致随后调用fgets()立即返回并只包含输入的那个字符。这是新程序员非常普遍的问题。
因此,虽然你可以像那样使用fscanf(),但通过使用fgets() + sscanf(),你可能可以避免一些头疼的问题。

这是正确的。另一方面,你会遇到一些麻烦,因为fgets会读取最大长度的一行,这可能会引起问题。想象一下,如果你的缓冲区只有10个字符(仅作为示例夸张),然后输入了:" 12345\n",你将收到" 12""345\n"。最安全的方法是使用循环fgetsrealloc来读取完整的一行,无论其大小。 - Shahbaz
@Shahbaz 我还是C语言的新手,但我尝试了一下发现"12345"没有按照你所解释的方式被分开("12"和"345\n")。相反,我在第一个变量中得到了"12345",而在第二个变量中得到了一些负数垃圾值。你想表达什么意思?为什么会这样? - ankush981
Shahbaz:对于简单的问题,选择足够大的缓冲区,读取前几个字符并在循环中忽略其他字符,直到读取到 '\n' 为止 :) - pmg
使用 fgets 函数时,必须指定有界缓冲区。如果在边界处有一个大小恰好为缓冲区大小的数字,那么它可能会被分割成两部分。请尝试使用 17 个空格后跟随一个六位数来输入数据进行测试。 - Shahbaz
1
@chux,如果一行开始变得非常长,当然可以生成失败。我的意思是,如果您想保持最大尺寸,这很好,那么您必须确保读取的行适合缓冲区,否则数据可能不可靠,您可能需要忽略已经读取的行的哪一部分以及仍然剩下的哪一部分。顺便说一句,通过耗尽其内存来使程序崩溃并不是真正的攻击。您可以限制其内存,或发送KILL信号或其他!这不是安全问题。 - Shahbaz
显示剩余3条评论

6
只使用fscanf()的问题在于错误管理。假设你向两个程序输入“51 years, 85 Kg”,第一个程序在sscanf()中失败,但你仍然可以使用line报告错误给用户,尝试不同的解析方法或进行其他操作;而第二个程序在years处失败,age可用,weight则无法使用。请记得始终检查*scanf()的返回值以进行错误检查。
    fgets(line, sizeof(line), stdin);
    if (sscanf(line, "%d%d", &age, &weight) != 2) /* error with input */;

编辑

在第一个程序中,发生错误后,输入缓冲区被清除;而在第二个程序中,输入缓冲区以YEAR开头......

在第一种情况下,恢复很容易;而在第二种情况下,需要通过某种方式清除输入缓冲区才能恢复。


3

当输入数据格式正确时,fscanf()fgets()/sscanf()没有区别。

  1. 发生两种错误:I/O错误和格式错误。 fscanf()同时处理这两种错误类型的一个函数,但提供的恢复选项很少。单独使用fgets()sscanf()允许将I/O问题与格式问题逻辑分离,因此更好的恢复。
  1. fscanf()只有1个解析路径。

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

  1. 没有嵌入的'\0'

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接