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

San*_*raj 2 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;
}
Run Code Online (Sandbox Code Playgroud)

只要用户输入整数值,上述代码就可以正常工作.例如,

$ ./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 
Run Code Online (Sandbox Code Playgroud)

但是,当用户进入任何意外的输入(如<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
Run Code Online (Sandbox Code Playgroud)

有一点需要注意:当使用<up-arrow>第一次进入时,它按预期工作!例如,

$ ./a.out 
Enter a number (0-9) :^[[A
Damn you!
$ 
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*wne 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
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ode 6

就个人而言,我建议scanf完全放弃交互式用户输入,尤其是数字输入。它只是不够健壮,无法处理某些不良情况。

%d转换符告诉scanf读取直到下一个非数字字符(忽略任何领先的空格)。假设调用

scanf("%d", &val);
Run Code Online (Sandbox Code Playgroud)

如果您的输入流看起来像 {'\n', '\t', ' ', '1', '2', '3', '\n'},scanf将跳过前导空白字符,读取并转换“ 123",并在结尾的换行符处停止。该值123将分配给valscanf并将返回值 1,表示成功分配的次数。

如果您的输入流看起来像 {'a', 'b', 'c', '\n'},scanf将在 处停止读取a,不向 分配任何内容val,并返回 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);
  }
}
Run Code Online (Sandbox Code Playgroud)