这是一个非常好的例子,说明为什么通常不应该使用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;
for (;;)
{
char *p;
printf( "Please enter a number: " );
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
number = strtol( line, &p, 10 );
if ( p == line )
{
printf( "Invalid input!\n" );
continue;
}
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Encountered invalid character!\n" );
goto continue_outer_loop;
}
}
break;
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 (;;)
{
char buffer[1024], *p;
long l;
fputs( prompt, stdout );
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "Line input was too long!\n" );
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Unrecoverable error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "Error converting string to number!\n" );
continue;
}
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "Number out of range error!\n" );
continue;
}
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Unexpected input encountered!\n" );
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;
}