在编程的时候,我们经常会要求用户从键盘输入数据,以增强程序的互动性。但是有时因为用户从键盘输入的数据不是程序想要的,会引发程序运行错误,轻则无法运行,重则引发系统崩溃。那么我们应该如何使用户输入数据合法化呢?
以这道题目为例:输入两个整型数,计算并输出两个整数的最大值。
题目要求用户键盘输入数据为整型数,即不能输入浮点数、字符等相对这道题目而言的非法数据。
首先来看函数scanf()。我们知道,如果函数scanf()调用成功(能正常读入输入数据),则其返回值为已成功读入的数据项数。也即如果我们使用一个整型变量ret接收函数scanf()的返回值,那么当ret的值为我们想要接收到的数据的数量(本题为2)时,即认为函数scanf()调用成功,否则不然。
基于此,我们可以写以下代码:
int a, b, ret;
printf("Input int a, b:");
ret = scanf("%d %d", &a, &b);
if (ret!=2)
{
printf("Input error!");
}
但是这样只能让程序运行一次,不论输入的数据合法与否,在输入一次数据之后程序都将终止运行,我们希望用户输入非法数据后继续让用户从键盘输入数据,需要用到循环语句:
int a, b, ret;
do
{
printf("Input int a, b:");
ret = scanf("%d %d", &a, &b);
}while (ret!=2);
此时我们尝试输入:
q↙
却发现程序无穷无尽地输出:
Input int a, b:
根本不给我们重新输入数据的机会。这是因为键盘缓冲区中仍有字符'q'与'\n'(回车符),函数scanf()一直无法读入它想要的数据,因此ret的值永远不等于2。据此,我们需要清空键盘缓冲区:
int a, b, ret;
do
{
printf("Input int a, b:");
ret = scanf("%d %d", &a, &b);
while (getchar()!='\n');
/*相当于:
while (getchar()!='\n')
{
;
}*/
}while (ret!=2);
当键盘缓冲区中仍有不为回车符的内容时,while (getchar()!='\n');便会一直运行,直至getchar()读取到回车符,清空键盘缓冲区。
也可以使用函数fflush()来清楚输入缓冲区中的内容,即把while (getchar()!='\n');一句改为fflush(stdin);。
此时我们再次尝试输入:
1.2 5↙
发现程序要求我们重新输入数据,即证明ret的值不为2,函数scanf()读入的数据没有2个。
但当我们输入:
5 1.2↙
却发现程序继续往下运行,说明ret的值为2,函数scanf()成功读入了2个数据,经调试我们发现a=5,b=1。为什么会这样呢?
这是因为键盘缓冲区中有'5'、空格、'1'、'.'、'2'五个内容,当函数scanf()读取到1后的'.'时发现不匹配,便停止了读入,但此时a和b已被成功赋值。
我们可以通过修改程序来防止这个bug发生:
int a, b, ret;
char c;
do
{
printf("Input int a, b:");
ret = scanf("%d %d", &a, &b);
c = getchar();
fflush(stdin);
}while ((ret!=2)||(c!='\n'));
这样,当用户输入的第二个数据为浮点数或以指数形式(如1e2)输入等其他带有字母的数据,甚至在输入第二个数字时手滑输入了其他非法数据,函数scanf()在把键盘缓冲区中非数字字符前面的数字全部读取完后进入下一行代码,此时c被赋值为非法字符(回车符除外),因为如果正确输入的话,如:
5 4↙
c一定会被赋值为回车符,符合循环条件,程序将要求用户重新输入数据。值得注意的是,此时并不能使用while (getchar()!='\n');来清空键盘缓冲区,因为如果是这样的话,即使用户正确输入了数据,如输入:
5 4↙
5和4被成功赋值给a和b,但回车符被赋值给c,while (getchar()!='\n');中的getchar()仍在等待用户输入数据,不会直接跳出循环。
不同问题有不同的处理策略,以上介绍的只是其中的一种,最重要的还是要了解函数scanf()与清空键盘缓冲区的方法。