C语言中有两类逻辑运算符,某些时候可以互换:按位运算符&、|和~,以及逻辑运算符&&、||、和!。如果程序员用其中一类的某个运算符替换掉另一类中对应的运算符,他也许会大吃一惊:互换之后程序看上云还能“正常”工作,但是实际上这只是巧合所致。
按位运算符&、|和~对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作。例如,10&12的结果是8(二进制表示为1000),因为运算符&按操作数的二进制表示逐位比较10(二进制表示为1010)和12(二进制表示为1000),当且仅当两个操作数的二进制表示的某位上同时是1时,最后结果的二进制表示中该位才是1.同样道理,10|12的结果是14(二进制表示为1110),而~10的结果是-11(二进制表示为11.110101),至少在二进制补码表示负数的机器上是这个结果。
另一方面,逻辑运算符&&、||和!对操作数的处理方式是将其视作要么是“真”,要么是“假”。通常约定将0视作“假”,而非0视作“真”。这些运算符当结果为“真”时返回1,当结果为“假”时返回0,它们只可能返回0或1.而且,运算符&&和运算符||在左侧操作数的值能够确定最终结果时根本不会对右侧操作数求值。
因此,我们能够很容易求得这个表达式的结果:!10的结果是0,因为10是非0数;10&&12的结果是1,因为10和12都不是0;10||12的结果也是1,因为10不是0.而且,在最后一个式子中,12根本不会被求值;在表达式10||f()中,f()也不会被求值。
考虑下面的代码段,其作用是在表中查询一个特定的元素:
i=0; while(i<tabsize&&tab[i]!=x) i++;
这个循环语句也有可能“正常”工作,但仅仅是因为两个非常侥幸的原因。
第一从此“侥幸“是,while中表达式&运算符的两侧都是比较运算,而比较运算的结果在为“真”是等于1,在为“假”时等于0.只要x和y的取值都限制在0或1,那第x&y和x&&y总是得出相同的结果。然而,如果两个比较运算中的任何一个用除1之外的非0数代表”真”,那么这个循环就不能正常工作了。
第二个“侥幸”时,对于数组结尾之后的下一个元素(实际上是不存在的),只要程序不云改变该元素的值,而仅仅读取它的值,一般情况下是不会有什么危害的。运算符&与运算符&&不同,运算符&两侧的操作数都必须被求值。所以在后一个代码段中,如果tabsize等于tab中的元素个数,当循环进入最后一次迭代时,即使i等于tabsize,也就是说数组元素tab[i]实际上并不存在,程序仍然会查看元素的值。
对于数组结尾之后的下一个元素,取它的地址是合法的。而我们试图去实际读取这个元素的值,这种做法的结果是未定义的,而且绝少有C编译器能够检测出这个错误。
未经允许不得转载:TacuLee » C陷阱与缺陷之运算符&&、||和!