为了回应我在
另一个关于C ++的答案中发表的观点:我建议放弃
scanf()
,永远不要使用它,而是使用
fgets()
和
sscanf()
。
原因是,在类Unix系统中,默认情况下,CLI程序运行的终端会在您的程序看到之前对用户输入进行一些处理。它会缓冲输入直到输入换行符,并允许进行一些基本的行编辑,例如使退格键起作用。
因此,您永远无法一次获取单个字符或几个单个字符,只能获取整行。但是这并不是例如
scanf(“%d”)
所处理的内容,它仅处理数字,
然后停止,将其余部分缓存在C库中,供未来的
stdio
函数使用。如果您的程序具有例如
printf("Enter a number: ");
scanf("%d", &a);
printf("Enter a word: ");
scanf("%s", word);
当你输入行
123 abcd
时,它会在换行符给出后同时完成两个
scanf()
,但第一个
scanf()
不会在用户按下空格时返回,即使这是数字结束的地方(因为此时该行仍在终端的行缓冲区中);第二个
scanf()
不会等待您再输入一行(因为输入缓冲区已经包含足以填充
%s
转换的内容)。
这不是用户通常期望的!
相反,他们希望按下回车键完成输入,如果您按下回车键,则会得到默认值或错误提示,可能建议您真正提供答案。
您无法使用
scanf("%d")
实现这一点。如果用户只按回车键,什么也不会发生。因为
scanf()
仍在等待数字。终端将该行发送到程序,但您的程序看不到它,因为
scanf()
已经吃掉了它。您没有机会对用户的错误做出反应。
这也不是很有用。
因此,我建议使用
fgets()
或
getline()
一次读取完整的输入行。这与终端给出的完全匹配,并且始终在用户输入一行后给您的程序控制权。您对输入行的处理取决于您,如果您想要一个数字,则可以使用
atoi()
、
strtol()
或甚至
sscanf(buf, "%d", &a)
来解析数字。
sscanf()
没有与
scanf()
相同的不匹配,因为它读取的缓冲区大小是有限的,当它结束时,它就结束了——该函数无法等待更多内容。
(对于支持如何跨越换行符等空格的文件格式,常规文件上的 fscanf()
也可以正常工作。对于面向行的数据,我仍然会使用 fgets()
和 sscanf()
。)
因此,不要使用我之前的内容,而是使用类似于以下内容:
printf("Enter a number: ");
fgets(buf, bufsize, stdin);
sscanf(buf, "%d", &a);
或者,实际上,还要检查sscanf()
的返回值,这样您就可以检测空行和其他无效数据:
#include <stdio.h>
int main(void)
{
const int bufsize = 100;
char buf[bufsize];
int a;
int ret;
char word[bufsize];
printf("Enter a number: ");
fgets(buf, bufsize, stdin);
ret = sscanf(buf, "%d", &a);
if (ret != 1) {
fprintf(stderr, "Ok, you don't have to.\n");
return 1;
}
printf("Enter a word: ");
fgets(buf, bufsize, stdin);
ret = sscanf(buf, "%s", word);
if (ret != 1) {
fprintf(stderr, "You make me sad.\n");
return 1;
}
printf("You entered %d and %s\n", a, word);
}
当然,如果你想让程序坚持下去,你可以创建一个简单的函数来循环使用
fgets()
和
sscanf()
,直到用户愿意按照指示操作;或者立即退出并显示错误。这取决于你认为如果用户不想跟着玩,你的程序应该怎么做。
你可以做类似的事情,例如循环使用
getchar()
读取字符直到换行符,在
scanf("%d")
返回后清除缓冲区中留下的任何垃圾,但这不能解决用户在空行上只按回车键的情况。无论如何,
fgets()
将读取直到换行符,所以你不必自己处理。
scanf()
之前使用fflush(stdin)
来读取单个字符。请阅读使用fflush(stdin)
,了解该方法的优缺点和替代方法的讨论(这在Windows上或多或少有效,在大多数其他地方则无效)。 - Jonathan Lefflerfflush(stdin)
是明显的未定义行为。 - Zakk