如何使用scanf验证输入

5
我该如何使用scanf验证用户输入?目前我的代码像这样,但是不起作用。
注意:我使用atoi只是为了验证scanf的验证是否有效。
scanf("%[0987654321.-]s",buf);

i = atoi(buf);

if(i)
    index = i;
6个回答

15

使用scanf()通常不是处理用户输入的好方法,因为失败会使FILE指针处于未知位置。这是因为scanf代表“扫描格式化”,而用户输入往往是非常非格式化的。

我建议使用fgets()获取一行输入,然后对字符串进行sscanf()检查和处理。

这也允许您检查字符串中包含的字符(可以使用循环或正则表达式),而scanf函数族并不适合这样做。

例如,使用scanf()"%d""%f"将在遇到第一个非数字字符时停止,因此无法捕捉尾随错误,如"127hello",它只会给你返回127。而使用非有界限制的%s则很容易引起缓冲区溢出问题。

如果您真的必须使用[]格式说明符(在scanfsscanf中),我认为它不应该跟在s后面。

对于一个使用上述建议的健壮输入解决方案,请参见这里。一旦您将输入行作为字符串,就可以使用sscanf处理它。


3
使用sscanf比scanf更好的建议是一个不错的想法。scanf(和sscanf)还有另一个问题,那就是数字如果超出转换类型的范围,会导致未定义行为,并且没有办法检查错误。通常最好使用fgets和strtol/strtod。 - Robert Gamble
如果您使用[]转换说明符,请务必包括最大字段宽度以避免缓冲区溢出! - Robert Gamble
Scanf是邪恶的简单体现。而fscanf更糟糕。Pax和Robert说得对,认真听他们的话。 - EvilTeach
-1 scanf做它该做的事情,对此完全可以接受。如果您无法阅读和理解文档,并期望它执行其他操作,则可能会成为您的问题。 - Chris Dodd
@Chris,scanf不是因为不能胜任自己的任务而不合适,而是因为它不能胜任人们试图用它做的事情。它只是一个不够强健的用户输入解决方案。顺便说一下,我会选择忽略结尾的人身攻击评论,我非常清楚大多数C语言的行为,但还是感谢你的关心 :-) - paxdiablo

6

您似乎想要验证一个字符串作为输入。这取决于您想要验证字符串是否包含双精度浮点数或整数。以下是检查双精度浮点数的方法(允许前导和尾随空格)。

bool is_double(char const* s) {
    int n;
    double d;
    return sscanf(s, "%lf %n", &d, &n) == 1 && !s[n];
}

sscanf会返回转换的项(不包括“%n”)。n将被设置为处理的输入字符数。如果所有输入都已处理,则s [n]将返回终止0字符。两个格式说明符之间的空格考虑了可选的尾随空格。

以下检查int,使用相同的技术:

bool is_int(char const* s) {
    int n;
    int i;
    return sscanf(s, "%d %n", &i, &n) == 1 && !s[n];
}

这里有一个问题在这里,其中还包括更多C++风格的做法,比如使用字符串流和来自boost的函数,例如lexical_cast等等。它们通常比scanf之类的函数更受欢迎,因为很容易忘记传递一些“%”给scanf或一些地址。scanf不会识别,而是会执行任意操作,而例如lexical_cast将在任何不正确的情况下抛出异常。


1

我的方法是将用户输入读入字符串,然后使用strtol()将其转换为长整型。 strtol()返回指向输入字符串中第一个无法处理的字符的指针,因此通过检查此字符,您可以知道是否解析了完整的字符串,就像这样:

char *string;
char *endptr;
long result;

scanf("%as", string);
errno = 0;
result = strtol(string, &endptr, 10);
if (ERANGE == errno)
    printf("Input number doesn't fit into long\n");
else if (*endptr)
    printf("%s is not a valid long number - it contains invalid char %c\n",
        string, *endptr);
else
    printf("Got %ld\n", result);

在这个片段中,scanf() 被指示使用格式修饰符 "a"(这是 GNU 扩展)自动分配足够大的空间来存储 string。如果你不在 GNU 上,需要手动分配 string 并在 scanf() 格式中限制最大大小。
这种方法可以更好地处理错误。

0

scanf的第一个参数中指定的是转换说明符scanf将尝试将输入转换为指定的格式,但有时您可能会对其匹配结果感到惊讶 :)

您应该进行的第一项检查是从scanf返回的值(它将给出匹配输入的数量)。 如果您没有得到预期的数字,则知道输入存在问题。 在您的情况下,应该是1。

if( scanf("%[0987654321.-]s", buf) == 1 )
{
      if( sanit_check( buf ) ) 
      {
      /* good input */
      }
      else
      {
      /* invalid input */
      }
}
else 
{
/* invalid input */
}

除此之外,您需要对您要求 scanf 进行的转换进行自己的合理性检查。


1
我更喜欢以下这个顺序:如果 (scanf() != 1) { error 1; } else if (!sanity_check()) { error 2: } else { use the good stuff }。 这样可以避免跳到页面的右侧,特别是在读取另一个值之后。 - Jonathan Leffler

-1
如果您想要读取一个数字,请使用:
int i;
scanf( "%d", &i );

如果你需要进行更复杂的检查,你就必须自己完成。Scanf不会为你完成。


-4
"buf" 需要是一个指针。看起来你正在按值传递。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接