C语言 验证do-while循环中的输入类型

to94eoyn  于 2023-01-01  发布在  其他
关注(0)|答案(5)|浏览(167)

基本上,我需要确保input是一个整数,如下所示:

do {
    printf("Enter > ");
    scanf("%d", &integer);
} while (/* user entered a char instead of an int */);

我尝试过各种方法,但当我尝试输入char时,总是以运行时错误或无限循环结束。我知道fflush(stdin)是未定义的行为,最好不要在我的代码中涉及它,以防止任何错误,加上由于某些原因,它在VS2015中不再工作
下面的代码是我尝试过的方法:

typedef enum {false, true} bool;
int ipt;
char c;
bool wrong_ipt;

do {
    c = '\0';
    printf("Enter > ");
    scanf("%d%c", &ipt, &c); //infinite loop occurs while a char has been entered
} while (c != '\n');

do {
    c = '\0';
    printf("Enter > ");
} while (scanf("%d", &ipt) != EOF);

do {
    wrong_ipt = false;
    do {
        ipt = NULL;
        printf("Enter > ");
        scanf("%d", &ipt);
        if (ipt == NULL) {
            wrong_ipt = true;
            break;
        }
    } while (ipt == NULL);
} while (wrong_ipt);

当用户在C中输入char时,除了fflush(stdin)之外,是否还有其他方法可用于防止无限循环?
谢谢

oxcyiej7

oxcyiej71#

问题是"scanf()"可能会在输入缓冲区中留下未读的数据,因此出现了"无限循环"。
另一个问题是您应该验证scanf()的返回值。如果您期望一个整数值...而scanf返回"0"个读取项...则您知道出了问题。
下面是一个例子:

#include <stdio.h>

void discard_junk () 
{
  int c;
  while((c = getchar()) != '\n' && c != EOF)
    ;
}

int main (int argc, char *argv[])
{
  int integer, i;
  do {
      printf("Enter > ");
      i = scanf("%d", &integer);
      if (i == 1) {
        printf ("Good value: %d\n", integer);
      }
      else {
        printf ("BAD VALUE, i=%i!\n", i);
        discard_junk ();
      }
   } while (i != 1);

  return 0;
}

输出示例:

Enter > A
BAD VALUE, i=0!
Enter > B
BAD VALUE, i=0!
Enter > 1
Good value: 1

"希望能有所帮助!

rryofs0p

rryofs0p2#

这是一个很好的例子,说明了为什么scanf通常不应该用于用户输入。
由于用户输入是基于行的,因此可以预期输入函数总是一次读取一行输入。但是,函数scanf的行为方式并非如此。相反,它只使用与%d转换格式说明符匹配所需的字符数。如果scanf无法匹配任何内容,则它根本不使用任何字符。这样,下一次对scanf的调用就会因为完全相同的原因而失败(假设使用了相同的转换说明符,并且您没有显式地丢弃无效的输入)。
在撰写本文时,其他三个答案通过检查scanf的返回值并显式丢弃无效输入来解决这个问题。这些答案中的(!)个具有这样的问题:它们例如接受"6sdfj23jlj"作为数字6的有效输入,尽管在这种情况下,整行输入显然应该被拒绝,这是因为scanf,如前所述,不会一次读取一行输入。
因此,解决这个问题的最佳方案可能是使用基于行的输入fgets,这样,您将总是一次只读取一行输入(假设输入缓冲区足够大,可以存储一整行输入)。读取该行之后,可以尝试使用strtol将其转换为数字。即使转换失败,输入行将从输入流中被消耗掉,因此您将不会遇到上面描述的大多数问题。
使用fgets的简单解决方案如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int main( void )
{
    char line[100];
    long number;

    //retry until user enters valid input
    for (;;) //infinite loop, equivalent to while(1)
    {
        char *p;

        //prompt user for input
        printf( "Please enter a number: " );

        //attempt to read one line of input
        if ( fgets( line, sizeof line, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to convert input to number
        number = strtol( line, &p, 10 );

        //verify that conversion was successful
        if ( p == line )
        {
            printf( "Invalid input!\n" );
            continue;
        }

        //verify that remainder of line only contains
        //whitespace, so that input such as "6sdfj23jlj"
        //gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Encountered invalid character!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        //input was valid, so break out of infinite loop
        break;

    //label for breaking out of nested loop
    continue_outer_loop:
        continue;
    }

    printf( "Input was valid.\n" );
    printf( "The number is: %ld\n", number );

    return 0;
}

注意goto语句通常不应该使用,但是在这种情况下,为了跳出嵌套循环,它是必要的。
此程序具有以下输出:

Please enter a number: 94hjj
Encountered invalid character!
Please enter a number: 5455g
Encountered invalid character!
Please enter a number: hkh7
Invalid input!
Please enter a number: 6sdfj23jlj
Encountered invalid character!
Please enter a number: 67
Input was valid.
The number is: 67

但是,这段代码还不完善,它还存在以下问题:
1.如果用户在一行中输入了100个字符,那么整行将无法放入输入缓冲区,在这种情况下,需要两次调用fgets来读取整行,程序将错误地将该行视为两个单独的输入行。
1.代码不会检查用户输入的数字是否可以用long表示(例如,数字是否过大),函数strtol通过相应地设置errno来报告这一情况(这是scanf所缺少的功能)。
这两个问题也可以通过执行额外的检查和错误处理来解决。但是,代码现在变得如此复杂,以至于将其全部放入自己的函数中是有意义的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

int get_int_from_user( const char *prompt )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfj23jlj" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

int main( void )
{
    int number;

    number = get_int_from_user( "Please enter a number: " );

    printf( "Input was valid.\n" );
    printf( "The number is: %d\n", number );

    return 0;
}
gstyhher

gstyhher3#

你的根本错误在于你从来没有告诉你的程序去消耗无效的输入。用语言来说,你告诉程序:

  • 如果有整数,则读取整数。(否则不读取任何内容)
  • 如果没有得到整数,请返回步骤1。

我猜你以为你做的是

  • 读取输入,如果它是整数,则存储它。
  • 如果不是整数,请返回步骤1。

所以你需要做的就是重写你的代码,让它做你想做的事情(或者像另一个答案一样,想出一些其他的方法),也就是说,把你的程序写成:

  • 读取一定量的输入。(例如一行)
  • 扫描输入以查看它是否为整数,并存储它。
  • 如果不是整数,请返回步骤1。

您可能会发现一些有用的相关函数是fgetssscanfatoi。例如,如果你打算阅读一行输入,确保你 * 确实这样做了 *,而且 * 正确 *。许多人都很懒,如果行很长,他们就会做错事情;例如,仅读取行的一部分,或导致缓冲区溢出)

monwx1rj

monwx1rj4#

格式说明符%d告诉scanf在命令行中需要一个整数值。当用户输入一行数据时,它被读取为字符串,然后scanf尝试了解输入的字符串是否可以解释为整数的十进制数字。
如果解释成功,那么找到的值将存储在作为参数传递的整型变量中。
scanf所做的rigth替换的次数由该函数以int值的形式获取,因为您只期望一个输入,值1将指示一切正常。
因此,如果出现错误,例如用户输入了一个无效的整数,scanf返回的数字小于格式说明符的数量。在这种情况下,小于1的值表示发生了错误:

int ipt, succeded;
 do {
    printf("ipt? ");
    succeded = scanf("%d", &ipt);
    if (succeded < 1) {    // Clean the input
      while (getchar() != '\n') 
        ;
    }
 } while(succeded < 1);
uurv41yg

uurv41yg5#

我知道我回答这个问题有点晚了,但是您可以减少循环的使用,而使用jump语句,然后只使用一个if语句来检查错误的输入,仍然可以获得相同的结果

enter: printf("Enter > ");
      i = scanf("%d", &integer);
      if (i != 1) {
        printf ("BAD VALUE, i=%i!\n", i);
        discard_junk ();
        goto enter:
      }
      printf ("Good value: %d\n", integer);

相关问题