C 当预期值为int类型而输入的值不是int类型时,如何验证用户输入?

C 当预期值为int类型而输入的值不是int类型时,如何验证用户输入?,c,error-handling,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) {

我有以下代码:

#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
,或任何字符串,如
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
需要注意的一点是:当用户第一次输入
时,它会按预期工作!例如

$ ./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 
$ ./a.out 
Enter a number (0-9) :^[[A
Damn you!
$ 

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

我的建议是检查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
}

我会使用
char
缓冲区来获取输入,然后使用
atoi
将其转换为整数。 这里唯一的问题是
atoi
在失败时返回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;
}
#包括
#包括
#包括
内部主(空)
{
int x;
char buf[4]={0};
scanf(“%s”,buf);
if(isdigit(buf[0]))
{
x=原子指数(buf);
如果(x>9)
{
//不好
}
其他的
{
//好的
}
}
其他的
{
//不好
}
返回0;
}
如果缓冲区的第一个元素不是数字,您就知道它的输入是错误的


否则,您现在用
atoi
检查值,看看它是否大于9。(您不需要检查较低的值,因为在
isdigt
调用中已经检测到-1(
buf[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;
}
#包括
#包括
#定义最小值0
#定义最大值9
int main()
{
int n,i;
而(1){
errno=0;
printf(“输入一个数字(%d-%d):”,最小值,最大值);
如果(scanf(“%d”,&n)!=1){
printf(“该死的!\n”);
打破
} 

如果(n>=MIN&&nscanf返回它读取的字段数,那么您可以执行以下操作


if(scanf(“%d”,&n)我个人建议,对于交互式用户输入,尤其是数字输入,完全放弃
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
分配任何内容,并返回0(表示未成功分配)

到目前为止,还不错,对吧?好吧,这里有一个丑陋的例子:假设您的用户键入“12w4”。您可能希望将整个输入视为无效而拒绝。不幸的是,
scanf
将愉快地转换并分配“12”,而保留“w4”在输入流中,阻塞下一次读取。它将返回1,表示分配成功

这是另一个丑陋的例子:假设您的用户键入了一个令人讨厌的长数字,如“12345678900123456789012345678901234567890”。同样,您可能希望完全拒绝此输入,但无论目标数据类型是否能代表该值,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);
  }
}
#包括
#包括
#包括
...
#定义位数…//目标数据类型的最大位数;
//例如,有符号16位整数最多有5位数字。
#定义BUFSIZ(数字)+3//符号字符、换行符和0终止符的帐户
...
字符输入[BUFSIZ];
如果(!fgets(输入,输入大小,标准输入))
{
//输入时读取错误-死机
出口(-1);
}
其他的
{
/**
*确保用户输入的字符串长度不超过缓冲区
*通过查找换行符调整大小以容纳。如果换行符
*如果不存在,则拒绝输入并从流中读取,直到
*我们看到一个换行符或得到一个错误。
*/
如果(!strhr(输入,'\n'))
{
//输入太长
而(fgets(input,sizeof input,stdin)和&!strchr(input,'\n'))
;
}
其他的
{
char*chk;
inttmp=(int)strtol(input,&chk,10);
/**
*chk指向未转换的第一个字符。如果
*如果是空白或0,则输入字符串是有效的
*整数
*/
如果(i空间(*
#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);
  }
}