在 do-while 循环中验证输入类型 C
Validate the type of input in a do-while loop C
基本上,我需要确保输入是一个整数,像这样:
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 = '[=11=]';
printf("Enter > ");
scanf("%d%c", &ipt, &c); //infinite loop occurs while a char has been entered
} while (c != '\n');
do {
c = '[=11=]';
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);
除了 fflush(stdin)
之外,还有什么可以用来防止用户在 C 中输入 char
时的无限循环?
谢谢
问题是“scanf()”可能会在您的输入缓冲区中留下未读数据。因此“无限循环”。
另一个问题是您应该验证 scanf()
中的 return 值。如果您期望一个整数值...并且 scanf returns "0" items read ... 那么您就知道出了问题。
这是一个例子:
#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
'希望对您有所帮助!
格式说明符 %d 告诉 scanf
命令行中需要一个整数值。当用户输入一行数据时,它被读取为一个字符串,然后 scanf
尝试了解输入的字符串是否可以解释为整数的十进制数字。
如果此解释成功,则由此找到的值将存储在您作为参数传递的整数变量中。
scanf
完成的正确替换次数由该函数以 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);
您的根本错误是您从不告诉您的程序使用无效输入。换句话说,你告诉程序:
- 如果有一个整数,则读取一个整数。 (否则什么都不读)
- 如果您没有得到整数,请返回第 1 步。
我假设你认为你正在做的是
- 读取输入,如果是整数,则存储它。
- 如果不是整数,返回步骤 1。
所以你需要做的是重写你的代码,让它做你想做的事情(或者想出一些其他的方法,如另一个答案)。即,将您的程序写入:
- 读取一些输入。 (例如一行)
- 扫描输入看是否为整数,并存储。
- 如果不是整数,返回步骤 1。
您可能会发现有用的一些相关函数是 fgets
、sscanf
和 atoi
。 (另外,尽量抵制编写错误代码的诱惑;例如,如果您打算读取一行输入,请确保您实际这样做,并且正确。很多人比较懒惰,如果行很长就会做错事;例如只读取部分行,或者导致缓冲区溢出)
我知道我回答这个问题有点晚了,但你可以减少循环的使用并改用跳转语句,然后只使用一个 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);
这是一个很好的例子,说明为什么 scanf
通常不应该用于用户输入。
由于用户输入是基于行的,因此人们会期望输入函数总是一次读取一行输入。但是,这不是函数 scanf
的行为方式。相反,它只消耗匹配 %d
转换格式说明符所需的字符数。如果 scanf
无法匹配任何内容,那么它根本不会消耗任何字符,因此对 scanf
的下一次调用将因完全相同的原因而失败(假设使用了相同的转换说明符并且您未明确丢弃无效输入)。这就是您的代码中发生的情况。
在撰写本文时,其他三个答案通过检查 scanf
的 return 值并明确丢弃无效输入来解决此问题。但是,这些答案中的所有三个(!)都存在问题,例如它们接受 "6sdfj23jlj"
作为数字 6
的有效输入,尽管在这种情况下显然应该拒绝整行输入。这是因为 scanf
,如前所述,不会一次读取一行输入。
因此,解决您的问题的最佳方法可能是使用 fgets
instead. That way, you will always read exactly one line of input at a time (assuming that the input buffer is large enough to store an entire line of input). After reading the line, you can then attempt to convert it to a number using 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 != '[=10=]'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Encountered invalid character!\n" );
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
但是,这段代码还不够完美。它仍然存在以下问题:
如果用户在一行中输入 100 个字符,那么整行将无法放入输入缓冲区。在这种情况下,需要两次调用 fgets
才能读取整行,并且程序会错误地将那一行视为两行单独的输入。
该代码不会检查用户输入的数字是否可以表示为 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 )
{
for (;;) //loop forever until user enters a valid number
{
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 error reading from input!\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 != '[=12=]'; 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;
}
基本上,我需要确保输入是一个整数,像这样:
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 = '[=11=]';
printf("Enter > ");
scanf("%d%c", &ipt, &c); //infinite loop occurs while a char has been entered
} while (c != '\n');
do {
c = '[=11=]';
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);
除了 fflush(stdin)
之外,还有什么可以用来防止用户在 C 中输入 char
时的无限循环?
谢谢
问题是“scanf()”可能会在您的输入缓冲区中留下未读数据。因此“无限循环”。
另一个问题是您应该验证 scanf()
中的 return 值。如果您期望一个整数值...并且 scanf returns "0" items read ... 那么您就知道出了问题。
这是一个例子:
#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
'希望对您有所帮助!
格式说明符 %d 告诉 scanf
命令行中需要一个整数值。当用户输入一行数据时,它被读取为一个字符串,然后 scanf
尝试了解输入的字符串是否可以解释为整数的十进制数字。
如果此解释成功,则由此找到的值将存储在您作为参数传递的整数变量中。
scanf
完成的正确替换次数由该函数以 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);
您的根本错误是您从不告诉您的程序使用无效输入。换句话说,你告诉程序:
- 如果有一个整数,则读取一个整数。 (否则什么都不读)
- 如果您没有得到整数,请返回第 1 步。
我假设你认为你正在做的是
- 读取输入,如果是整数,则存储它。
- 如果不是整数,返回步骤 1。
所以你需要做的是重写你的代码,让它做你想做的事情(或者想出一些其他的方法,如另一个答案)。即,将您的程序写入:
- 读取一些输入。 (例如一行)
- 扫描输入看是否为整数,并存储。
- 如果不是整数,返回步骤 1。
您可能会发现有用的一些相关函数是 fgets
、sscanf
和 atoi
。 (另外,尽量抵制编写错误代码的诱惑;例如,如果您打算读取一行输入,请确保您实际这样做,并且正确。很多人比较懒惰,如果行很长就会做错事;例如只读取部分行,或者导致缓冲区溢出)
我知道我回答这个问题有点晚了,但你可以减少循环的使用并改用跳转语句,然后只使用一个 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);
这是一个很好的例子,说明为什么 scanf
通常不应该用于用户输入。
由于用户输入是基于行的,因此人们会期望输入函数总是一次读取一行输入。但是,这不是函数 scanf
的行为方式。相反,它只消耗匹配 %d
转换格式说明符所需的字符数。如果 scanf
无法匹配任何内容,那么它根本不会消耗任何字符,因此对 scanf
的下一次调用将因完全相同的原因而失败(假设使用了相同的转换说明符并且您未明确丢弃无效输入)。这就是您的代码中发生的情况。
在撰写本文时,其他三个答案通过检查 scanf
的 return 值并明确丢弃无效输入来解决此问题。但是,这些答案中的所有三个(!)都存在问题,例如它们接受 "6sdfj23jlj"
作为数字 6
的有效输入,尽管在这种情况下显然应该拒绝整行输入。这是因为 scanf
,如前所述,不会一次读取一行输入。
因此,解决您的问题的最佳方法可能是使用 fgets
instead. That way, you will always read exactly one line of input at a time (assuming that the input buffer is large enough to store an entire line of input). After reading the line, you can then attempt to convert it to a number using 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 != '[=10=]'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Encountered invalid character!\n" );
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
但是,这段代码还不够完美。它仍然存在以下问题:
如果用户在一行中输入 100 个字符,那么整行将无法放入输入缓冲区。在这种情况下,需要两次调用
fgets
才能读取整行,并且程序会错误地将那一行视为两行单独的输入。该代码不会检查用户输入的数字是否可以表示为
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 )
{
for (;;) //loop forever until user enters a valid number
{
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 error reading from input!\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 != '[=12=]'; 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;
}