我有以下代码:
#include <stdio.h>
#define MIN 0
#define MAX 9
int main()
{
int n;
while (1) {
printf("Enter a number (%d-%d) :", MIN, MAX);
scanf("%d", &n);
if (n >= MIN && n <= MAX) {
printf("Good\n");
} else {
printf("Damn you!\n");
break;
}
}
return 0;
}
只要用户输入整数值,上述代码就可以正常工作。例如,
$ ./a.out
Enter a number (0-9) :15
Damn you!
$ ./a.out
Enter a number (0-9) :5
Good
Enter a number (0-9) :3
Good
Enter a number (0-9) :-1
Damn you!
$ ./a.out
但是,当用户输入任何意外的输入(例如<up-arrow>
- ^[[A
或任何字符串,如abc
或abc def
等)时,它会输入进入无限循环。
$ ./a.out
Enter a number (0-9) :2
Good
Enter a number (0-9) :^[[A
Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
^C
有一点需要注意:当用户第一次进入<up-arrow>
时,它会按预期工作!例如,
$ ./a.out
Enter a number (0-9) :^[[A
Damn you!
$
为什么这种奇怪的行为?我们该如何处理用户输入不合适的内容的情况?
答案 0 :(得分:12)
我的建议是检查scanf()的返回值。如果为零,则存在匹配失败(即用户未输入整数)。
它成功的原因是因为当匹配失败时,scanf()不会改变n,所以检查是在未初始化的'n'上执行的。我的建议 - 始终是将所有内容初始化,这样你就不会像在那里那样得到奇怪的逻辑结果。
例如:
if (scanf("%d",&n) != 1))
{
fprintf(stderr,"Input not recognised as an integer, please try again.");
// anything else you want to do when it fails goes here
}
答案 1 :(得分:4)
就我个人而言,我建议完全抛弃scanf
用于交互式用户输入,尤其是数字输入。它只是不够强大,无法处理某些不良案件。
%d
转换说明符告诉scanf
读取下一个非数字字符(忽略任何前导空格)。假设呼叫
scanf("%d", &val);
如果您的输入流看起来像{'\ n','\ t','','1','2','3','\ n'},scanf
将跳过引导空白字符,读取并转换“123”,并在尾随换行符处停止。值123
将分配给val
,scanf
将返回值1,表示成功分配的数量。
如果您的输入流看起来像{'a','b','c','\ n'},scanf
将停止在a
处读取,而不会向{val
分配任何内容1}},并返回0(表示没有成功分配)。
到目前为止,这么好,对吗?好吧,这是一个丑陋的案例:假设您的用户键入“12w4”。你可能想拒绝这整个输入无效。不幸的是,scanf
将很乐意转换并分配“12”并在输入流中保留“w4”,从而导致下一次读取。它将返回1,表示分配成功。
这是另一个丑陋的案例:假设您的用户输入了一个令人讨厌的长号,例如“1234567890123456789012345678901234567890”。同样,您可能希望完全拒绝此输入,但scanf
将继续转换并分配它,无论目标数据类型是否可以表示该值。
要正确处理这些情况,您需要使用其他工具。更好的选择是使用fgets
将输入作为文本读取(防止缓冲区溢出),并使用strtol
手动转换字符串。优点:你可以检测和拒绝诸如“12w4”之类的坏字符串,你可以拒绝显然太长且超出范围的输入,并且你不会在输入流中留下任何垃圾。缺点:这是一项更多的工作。
以下是一个例子:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
...
#define DIGITS ... // maximum number of digits for your target data type;
// for example, a signed 16-bit integer has up to 5 digits.
#define BUFSIZ (DIGITS)+3 // Account for sign character, newline, and 0 terminator
...
char input[BUFSIZ];
if (!fgets(input, sizeof input, stdin))
{
// read error on input - panic
exit(-1);
}
else
{
/**
* Make sure the user didn't enter a string longer than the buffer
* was sized to hold by looking for a newline character. If a newline
* isn't present, we reject the input and read from the stream until
* we see a newline or get an error.
*/
if (!strchr(input, '\n'))
{
// input too long
while (fgets(input, sizeof input, stdin) && !strchr(input, '\n'))
;
}
else
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
/**
* chk points to the first character not converted. If
* it's whitespace or 0, then the input string was a valid
* integer
*/
if (isspace(*chk) || *chk == 0)
val = tmp;
else
printf("%s is not a valid integer input\n", input);
}
}
答案 2 :(得分:2)
我会使用char
缓冲区来获取输入,然后将其转换为一个整数,例如: atoi
。
这里唯一的问题是atoi
失败时返回0(由于失败或因为值为0,您无法确定它是否为0)。
您也可以将字符串与strncmp
进行比较。
//编辑:
根据评论中的建议,您可以使用isdigit()
进行检查
由于我有点匆忙,我无法在你的用例中实现我的例子,但我也怀疑这会导致任何麻烦。
一些示例代码是:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(void)
{
int x;
char buf[4] = {0};
scanf("%s",buf);
if(isdigit(buf[0]))
{
x = atoi(buf);
if( x > 9)
{
// Not okay
}
else
{
// okay
}
}
else
{
// Not okay
}
return 0;
}
如果缓冲区的第一个元素不是数字,那么无论如何都要知道它的错误输入。
否则,您现在使用atoi
检查值,看看它是否大于9.(您不需要检查较低的值,因为在isdigt
调用中已经检测到-1( buf[0]
将是“ - ”)
答案 3 :(得分:0)
我已按如下方式更新了代码(选中scanf()
返回值)并且工作正常。
#include <stdio.h>
#include <errno.h>
#define MIN 0
#define MAX 9
int main()
{
int n, i;
while (1) {
errno = 0;
printf("Enter a number (%d-%d) :", MIN, MAX);
if (scanf("%d", &n) != 1) {
printf("Damn you!\n");
break;
}
if (n >= MIN && n <= MAX) {
printf("Good\n");
} else {
printf("Damn you!\n");
break;
}
}
return 0;
}
以下是scanf()
手册页中的一些注意事项!
man scanf
格式字符串由一系列指令组成,这些指令描述了如何处理输入字符序列。如果指令处理失败,则不再读取其他输入,并返回scanf()。 “失败”可以是以下任一种:输入失败,意味着输入字符不可用,或匹配失败,意味着输入不合适。
返回值: scanf返回成功匹配和分配的输入项的数量,可以少于提供的数量,或者在早期匹配失败的情况下甚至为零。如果在第一次成功转换或匹配失败发生之前达到输入结束,则返回值EOF。如果发生读取错误,也会返回EOF,在这种情况下,将设置流的错误指示符,并设置errno指示错误。
答案 4 :(得分:0)
scanf返回它读取的字段数,因此您可以执行类似
的操作 if (scanf("%d",&n)<1) exit(1)
甚至:
while(scanf("%d",&n)!=1);