先来一段白痴式代码(idiot.c),由易入难,以体现我一贯的思维严谨性:
int age; scanf("%d", &age); printf("哇!您 %d 岁了!\n", age);
上述代码作何解读? 简单,就是让你从标准输入设备(也就是键盘),敲入一个十进制整数,然后放进变量 age 之中。然后做一惊一乍状爆出你的年龄。
既然本文面向C语言初学者,我也不怕做个长舌妇,把话说得更加完(luo)满(suo)一点,来提几个找抽的问题:
① 为什么是从键盘输入?
② 为什么是十进制整数?
③ 如果我就是要胡乱输入,你奈我何?(划重点)
不急,来一拳拳抡死这个智障
① 为什么是从键盘输入? 因为 scanf() 函数默认就是从键盘读取数据呀!好吧,这个回答可能会觉得索然无味,但如果我们认识 scanf() 的其他几个同门亲兄弟的话,可能感觉会有点不一样,他们是:
sscanf(); // 专门从某块内存读取数据 fscanf(); // 专门从某个文件读取数据
因此你现在知道,scanf() 只是家族众多兄弟中的一员,大家各有所好而已,没错,scanf() 就是专门从键盘读取数据的那个家伙。
② 为什么是十进制整数?因为代码中的 %d 就是 decimal 的首字母,这表明此时 scanf() 就是希望你输入一个十进制整数,这个 %d 就是所谓的格式控制符。那你会问了,如果希望输入别的什么进制的整数呢?或者浮点数、字符串呢?你猜到了,那将会有不同的控制符来表示,比如:
有了上表,可见我没骗你,%d 真是输入十进制整数的意思!
③ 如果我就是要胡乱输入,你奈我何?这个问题是本文要讨论的重点,先来看看一个很皮的家伙,是怎么戏弄上面这段程序的:
当某人输入二百五的时候,这段程序很老实地说他已经250岁了,虽然看起来无可指责,毕竟年龄是他自己输入进去的,但我们总会觉得这个程序缺少一点脑筋,正常来讲它应该要把人的年龄限制在一个合理的范围,比如:1 - 100岁之间。当人类输入一个不合理的年龄的时候,程序应该要能指出人类的愚蠢,很可惜我们的白痴程序没能做到这一点。
解决这个BUG比较简单,只要做个数值判断就可以了:
int age; scanf("%d", &age); if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);
再来看更离谱的错误:
当某个人类输入一个完全不是年龄的东西的时候,程序彻底傻X了,输出了一个完全不合理的非法年龄,你可以理解为:程序陷入了迷乱。
接下来,我们要改造一下程序,使之具备一定的智能。但在此之前,需要对 scanf() 的来龙去脉理清头绪。
首先,当我们说函数 scanf() 是从键盘获取数据的时候,我们要承认这个说法是不严谨的,严格讲,scanf() 只是从键盘对应的文件的缓冲区中读取数据,而无法直接读取键盘敲入的数据,可以想象,键盘到 scanf() 中间有一段路程要求,要完美讲清楚这个过程显然要画出图来,以示诚意,是时候展现我的绘画才艺了,请欣赏:
对上图做点解释:
① 手指敲击键盘时,数据由键盘的驱动程序读取,并被保存在驱动程序中,此时跟scanf()没有半毛钱关系。
② 输完了并敲击回车键后,驱动程序将数据送往缓冲区,并通知 scanf() 来搬运数据。
③ scanf() 带着参数 %d 来到缓冲区,跟缓冲区中的数据格式对了对眼神,如果发现格式没错,那就搬走,放到你指定的 age 里面,如果格式不对,那 scanf() 将一走了之,不干任何事情。
④ 如果scanf() 成功搬运了一个数据,那就返回1,如果成功搬运了两个数据,那就返回2,如果没跟任何数据对上眼神,就返回0。
有了以上的工作流程,我们就可以改进上面的 idiot.c ,改成 regular.c。我们可以通过判断 scanf() 的返回值,来知道它究竟搬运了数据没有:
int age; if ( scanf("%d", &age) != 1) printf("叫你输入整数,别整些没用的!\n"); else if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);
来试试效果:
现在程序有点像正常人了,但这还不够,还有个不大不小的BUG,请看:
输入 23abc 本来应该是一个不正经的年龄,但是程序没报错,而是直接将前面的23读取并汇报了年龄。换句话讲,当输入 23abc 的时候,scanf() 是正常工作的,它返回了 1,正常拿到了整数数据并搬到了 age 里,只不过留下了未能匹配格式的 abc 在缓冲区中没有收拾,造成以上BUG。
这个问题的解决,就不能简单地判断 scanf() 的返回值,而是在他返回正常的数据个数之后,还要判断缓冲区中是否还残留有非法格式的数据,这个怎么判断呢?这就要用另一个函数了,就是它:
getchar();
这个函数的作用,就是读取缓冲区中的一个字符。你想,如果我们输入的是正常的年龄,比如 23 ,那么当 scanf() 把 23 搬走之后,缓冲区中必然留下的是一个回车键,即 "\n",否则,缓冲区中必然会留下其他的字符,根据这个思路立刻修改程序,变身为 smart.c :
int age; if ( scanf("%d", &age) != 1 ) printf("叫你输入整数,别整些没用的!\n"); else if ( getchar() != '\n' ) printf("数字后面别拖个尾巴,OK?\n"); else if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);
最后测试一下程序: