【问题标题】:How validate user input when the expected value is of type int and the entered value is not of type int?当期望值是 int 类型并且输入的值不是 int 类型时,如何验证用户输入?
【发布时间】:2012-02-07 16:20:08
【问题描述】:

我有以下代码:

#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 

但是,当用户输入任何意外输入时(如&lt;up-arrow&gt; - 这是^[[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

需要注意的一点:当用户第一次输入&lt;up-arrow&gt;时,按预期工作!例如,

$ ./a.out 
Enter a number (0-9) :^[[A
Damn you!
$ 

为什么会出现这种奇怪的行为?用户输入了不合适的内容,我们应该如何处理?

【问题讨论】:

  • 如果您想要防弹输入,那么您需要先将输入作为字符串读取并验证它

标签: c error-handling


【解决方案1】:

我的建议是检查 scanf() 的返回值。如果为零,则表示匹配失败(即用户没有输入整数)。

它成功的原因是当匹配失败时 n 没有被 scanf() 改变,所以检查是在一个未初始化的 '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 但添加示例以获得更好的答案,例如:if (scanf("%d", &amp;n) != 1) break;
【解决方案2】:

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

%d 转换说明符告诉scanf 读取到下一个非数字字符(忽略任何 前导 空格)。假设调用

scanf("%d", &val);

如果您的输入流看起来像 {'\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);
  }
}

【讨论】:

  • 感谢您的精彩解释!它确实提高了我对scanf() 的理解。谢谢你的课。 +1!
【解决方案3】:

我会使用char 缓冲区来获取输入,然后将其转换为整数,例如atoi。 这里唯一的问题是atoi在失败时返回0(你无法确定它是0是因为失败还是因为值为0)。

您也可以将字符串与strncmp 进行比较。

// 编辑:

根据 cmets 的建议,您可以使用 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] 将是“-”)

【讨论】:

  • 也许在用atoi()转换之前,用isnumber()控制char缓冲区的所有元素可以解决这个问题。
【解决方案4】:

我已将代码更新如下(检查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 来指示错误。

【讨论】:

  • 当心输入前缀是数字的输入(例如,“1234wxyz”)。我不确定 scanf() 如何处理这个问题。如果没有,最好使用其他人建议的方法:将输入行读入字符串并使用 strtol() 或 strtod() 转换数字。这两个函数还返回指向第一个无效字符的指针,因此您可以判断整个输入字符串是否已被转换消耗。
【解决方案5】:

scanf 返回它读取的字段数,因此您可以执行类似的操作

if (scanf("%d",&amp;n)&lt;1) exit(1)

甚至:

while(scanf("%d",&n)!=1);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-25
    • 2017-02-25
    • 1970-01-01
    • 1970-01-01
    • 2018-07-07
    • 2010-12-04
    • 2014-12-09
    • 1970-01-01
    相关资源
    最近更新 更多