当期望值是int类型且输入的值不是int类型时,如何验证用户输入?

时间:2012-01-11 09:42:17

标签: c error-handling

我有以下代码:

#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或任何字符串,如abcabc 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!
$ 

为什么这种奇怪的行为?我们该如何处理用户输入不合适的内容的情况?

5 个答案:

答案 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将分配给valscanf将返回值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);