最简单的选择是在scanf() 的格式字符串中添加一个前导空格。例如,如果您想在输入错误时提示用户再次输入字符,您可以这样做:
#include <stdio.h>
int main(void)
{
char tmp;
printf("Enter \"p\" if you want to sort and shuffle a list of players "
"or enter \"s\" if you want to sort a list of slots: "); // prompt
while (scanf(" %c", &tmp) != 1 || (tmp != 'p' && tmp != 's')) {
printf("Please enter \"p\" or \"s\": ");
}
printf("You chose %c\n", tmp);
printf("Choose \"a\"scending or \"d\"escending sort: ");
while (scanf(" %c", &tmp) != 1 || (tmp != 'a' && tmp != 'd')) {
printf("Please enter \"a\" or \"d\": ");
}
printf("You chose %c\n", tmp);
return 0;
}
不过,这种简单的方法存在问题。如果用户输入"aaap",那么结果输出将是:
Enter "p" if you want to sort and shuffle a list of players or enter "s" if you want to sort a list of slots: aaap
Please enter "p" or "s": Please enter "p" or "s": Please enter "p" or "s": You chose p
Choose "a"scending or "d"escending sort:
或者,更糟糕的是,如果用户输入了正确的第一个字符,然后是其他字符,例如"sfffa":
Enter "p" if you want to sort and shuffle a list of players or enter "s" if you want to sort a list of slots: sfffa
You chose s
Choose "a"scending or "d"escending sort: Please enter "a" or "d": Please enter "a" or "d": Please enter "a" or "d": You chose a
因为scanf() 在输入流中留下了不匹配的字符,所以必须在再次读取输入流之前处理这些字符。在使用" %c" 的简单解决方案中,初始空格会导致scanf() 跳过前导空白字符,但会拾取任何其他字符。而在现实世界中,您不能指望用户通过提供良好的输入进行合作。
一种典型的可移植解决方案是在循环中使用getchar() 以在输入操作后清除输入流。您可能需要稍微更改输入循环逻辑才能执行此操作:
#include <stdio.h>
...
char tmp;
int c;
int scanf_ret;
do {
printf("Enter \"p\" if you want to sort and shuffle a list of players "
"or enter \"s\" if you want to sort a list of slots: "); // prompt
scanf_ret = scanf("%c", &tmp);
while ((c = getchar()) != '\n' && c != EOF) {
continue; // discard extra characters
}
} while (scanf_ret != 1 || (tmp != 'p' && tmp != 's'));
在调用scanf() 之后,进入一个循环,读取并丢弃输入流中剩余的任何字符。请注意c 是int,并且EOF 在循环中被显式测试。 getchar() 函数可能会在发生错误时返回 EOF,或者如果用户从键盘发出 EOF 信号,或者如果输入已从文件重定向。在这种情况下未能测试EOF 将导致无限循环。
即使用户输入了不可预知的输入,该解决方案仍然有效,因为输入流总是在获得输入后被清除。请注意,格式字符串中不需要前导空格。另一种解决方案是使用fgets() 将一行输入读入缓冲区,并使用sscanf() 解析缓冲区。
#include <stdio.h>
#include <stdlib.h>
...
char buffer[1000];
char tmp;
int sscanf_ret;
do {
printf("Enter \"p\" if you want to sort and shuffle a list of players "
"or enter \"s\" if you want to sort a list of slots: "); // prompt
if (fgets(buffer, sizeof buffer, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
sscanf_ret = sscanf(buffer, "%c", &tmp);
} while (sscanf_ret != 1 || (tmp != 'p' && tmp != 's'));
fgets() 函数通过换行符获取整行输入,或者如果缓冲区不足以容纳输入流中的所有字符,则从输入流中获取 (sizeof buffer) - 1 字符和 @987654345 @终结者。通过提供一个大尺寸的缓冲区,这应该可以正常工作。用户仍然可以输入大量字符,虽然这不会溢出缓冲区,但字符会留在输入流中以供下一次读取操作查找。为避免此类问题,如果还有多余的字符,则应在调用fgets() 后使用上面的getchar() 循环清除输入流。
所有这些技术都有自己的位置。显然最后两个比第一个更健壮,并且由于用户不太可能在输入提示符处输入 999 个字符(包括换行符),因此它们几乎处于同等地位。但是,为避免所有意外,最好在获得用户输入后显式清除输入流。