在C语言中,从用户获取输入的最佳方式是什么?

57
许多人说在“更严肃的程序”中不应使用scanf,同样也不应使用getline
我开始迷茫:如果我遇到的每个输入函数都被人说不能使用,那我应该使用什么?有没有更“标准”的获取输入的方法我不知道的?

1
这非常取决于你的程序要做什么。我建议你(几乎在任何情况下)使用scanf,并检查其返回值。 - asaelr
3
在C语言中,scanf是获取格式化输入的标准方法,而fgets/fgetc则是推荐的用于获取整行或单个字符的标准函数。大多数其他函数要么是非标准的,要么是特定于平台的。 - Some programmer dude
2
在一个严肃的程序中,没有理由不使用getline函数。如果你的平台没有它,你可以用fgetsmalloc来实现它。 - Fred Foo
C/C++中需要避免的事项:http://www.gidnetwork.com/b-59.html,http://www.gidnetwork.com/b-62.html,http://www.gidnetwork.com/b-60.html,http://www.gidnetwork.com/b-63.html和http://www.gidnetwork.com/b-64.html。 - Fred Larson
1
请阅读友好的C语言常见问题解答(FAQ):http://c-faq.com/stdio/scanfprobs.html。 - Lundin
1
还有阅读此文 - Lundin
4个回答

51

通常情况下,fgets() 被认为是一个不错的选择。它可以将整行读入缓冲区,然后你可以对其进行必要的操作。如果您想要类似于 scanf() 的行为,则可以将所读取的字符串传递给 sscanf()

这种方法的主要优点是,如果转换失败,很容易进行恢复,而使用 scanf() 则会在 stdin 上留下输入需要清空的情况。此外,您不会陷入将逐行输入与 scanf() 混合使用的陷阱中,在这种情况下,例如 \n 通常会被留在 stdin 上,往往会导致新手编码人员误以为输入调用已被完全忽略。

以下示例可能会符合您的需求:

char line[256];
int i;
if (fgets(line, sizeof(line), stdin)) {
    if (1 == sscanf(line, "%d", &i)) {
        /* i can be safely used */
    }
}

需要注意的是,fgets()在遇到文件结尾或错误时返回NULL,这就是为什么我用if将其包裹的原因。而sscanf()的调用结果表示成功转换的字段数量。

要记住,如果行的长度超过了缓冲区,fgets()可能无法读取整行内容,在“严肃”程序中,这是您应该考虑的事情。


1
加上fflush(stdin)fgets()之后如何?这样可以确保输入缓冲区过满(超过255个字符)不会影响对fgets()的后续调用。 - Twonky
2
@Twonky:就C语言而言,“fflush(stdin)”是未定义的(尽管在某些平台上确实会执行您所建议的操作)。但是,是的,从这方面来看它可以变得更加健壮。 - FatalError

12

对于可以设置输入长度限制的简单输入,我建议使用 fgets() 从终端读取数据。

这是因为 fgets() 允许您指定缓冲区大小(相比之下,gets() 几乎不应该用于从人类读取输入,原因就在于这一点):

char line[256];

if(fgets(line, sizeof line, stdin) != NULL)
{
  /* Now inspect and further parse the string in line. */
}
记住它将保留例如换行符等特殊字符,这可能会令人惊讶。
更新:如评论所指出的那样,如果您愿意承担跟踪内存的责任,则有更好的选择:getline()。对于 POSIX 代码而言,这可能是最佳的通用解决方案,因为它没有对要读取的行长度进行任何静态限制。

“简单的输入,可以设置输入长度的固定限制”听起来不像是“严肃”的程序。POSIX getline函数更加灵活。 - Fred Foo
如果我可以为我的问题选择两个获胜者,你将成为第二个,因为你解释了getline()的细节和gets的行为。但是FatalError的答案更详细地介绍了如何使用这种技术。 - user1115057

7

使用scanf存在以下几个问题:

  • 使用普通的%s格式符读取文本与使用gets()函数一样存在风险,如果用户输入的字符串长度超过目标缓冲区大小,会导致缓冲区溢出;

  • 如果使用%d%f格式符读取数字输入,则无法完全捕获和拒绝某些不良输入格式 -- 如果使用%d读取整数,而用户输入"12r4",scanf会将12转换并分配,而将r4留在输入流中,从而破坏下一个读取操作;

  • 某些格式符跳过前导空格,而另一些则不跳过,如忽略这一点可能会导致一些输入被完全跳过;

基本上,使用scanf来保证输入的鲁棒性需要非常大的额外努力。

一个好的替代方案是使用fgets()读取所有输入作为文本,然后使用sscanf,或结合使用strtokstrtolstrtod等函数来标记并转换输入。


1
谢谢,现在我知道为什么人们说scanf可能不好的原因了。 - user1115057

3

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