K&R《C语言》书中的一个Bug
最近在重温K&R的C语言圣经,第二章中的练习题2-2引起了我的注意。
原题是:
Write a loop equivalent to the for loop above without using && or ||.
题目里说的for循环是下面这个:
for (i=0; i < lim-1 && (c=getchar()) != '\n' && c != EOF; ++i) s[i] = c;
不能用&&和||运算符,又要与for循环中的3个条件表达式等价,怎么改呢?
因为书中这一节正在讲关系运算符和逻辑运算符,其中提到了当表达式结果为true时的值实际上是整数1,为false时为0,即:
true == 1 false == 0
所以我想到了一种方法:
for (i=0; (i<lim-1) + ((c=getchar())!='\n') + (c!=EOF) == 3; ++i) s[i] = c;
3个表达式都成立,也就是说它们的值都是1,所以和为3。
看起来好像没问题,但是我发现这么修改却是不对的。
因为在原程序里的3个表达式是有先后次序并且有“短路”关系的——当第一个表达式“i<lim-1”为false时,后面两个就不会再执行了;而且也不应该执行,否则就会导致字符丢失。
为什么呢?假如现在缓冲区里有字符“abcdefg”,lim为3:
当i为2时,“i<lim-1”为false,如果程序是非短路的,此时会继续执行后面的c=getchar(),就会把第3个字符即"c"读取出来,但由于整个循环的条件为false,因此不会执行赋值语句“s[i]=c”,所以这个字符就这么丢失了。
而我修改后的程序恰恰是非短路的!但我却想不出来一个可以和原程序完全等价的方案。
评论中@剑指天涯不可挡 和 @nicozhang 两位朋友指出可以用条件表达式或将条件判断放到循环体中来解决这个问题。但是书中到目前为止还没有介绍if语句和条件表达式。如果这个题目是想考察if语句或条件表达式,那么它就不应该在这里出现。所以我觉得这是一个作者也没有考虑到的Bug,你不可能写出和原程序完全等价的程序而不用到&&和||。
毕竟两位作者也是人。
当然也可能是我错了,如果你有好的解决方案请不吝赐教。
20151016补充:
今天回过头来看这篇文章,重新思考这个问题,忽然想到一个解决方法:
// for (i=0; i < lim-1 && (c=getchar()) != '\n' && c != EOF; ++i) // s[i] = c; for (i=0; (i < lim-2) + ((s[i]=getchar()) != '\n') + (s[i] != EOF) == 3; ++i) ;
把c用s[i]代替就不会出现上面所说的字符丢失的问题了。并且此时必须注意,第一个判断i < lim-1需要修改为i < lim-2。因为修改后的条件表达式不是短路的,如果不这样修改,那么当i等于lim-1时并不会马上退出循环,而是会继续执行后面两个子表达式,然后才退出循环,这样程序就错了。
因此这道题并没有Bug,2位大师是冤枉的,我在此郑重向他们道歉!