4.1.2 逻辑运算符和逻辑表达式
1.基本概念
用逻辑运算符将关系表达式连接起来,就构成了逻辑表达式。例如,如果希望a>3并且b>4,这里的“并且”,就是逻辑运算符,那么,整个(a>3并且b>4)就是一个逻辑表达式,逻辑表达式的值,也是“真”或者“假”,也就是1或者0。
C语言提供了三种逻辑运算符,如表4.2所示。
表4.2 C语言提供的三种逻辑运算符
在表4.2中,“&&”和“||”是双目运算符,也就是要求有两个运算分量,例如(a>b)&&(c>d),左边(a>b)是第一个运算分量,右边(c>d)是第二个运算分量,同理,再如(a>b)||(c>d),左边(a>b)是第一个运算分量,右边(c>d)是第二个运算分量。
“!”是单目运算符,只要求一个运算分量,这个运算分量放在“!”的后面。例如!(a>b),这里的(a>b)整个看成是一个运算分量。
下面用a来代表一堆表达式,用b来代表另一堆表达式,则有如下针对三种逻辑运算符的运算规则说明:
(1)a&&b:若a和b都为真,则a&&b才为真,否则为假。
(2)a||b:若a和b有一个为真,则a||b为真,否则为假。
(3)!a:若a为真则!a为假,若a为假则!a为真。
将上述三条规则整理成一个表格,称为逻辑运算真值表,如表4.3所示。
表4.3 逻辑运算真值表
可以对表4.3中的三种逻辑运算符“!”“&&”“||”总结一些规律:
(1)针对“&&”运算符,全真出真,有假出假,也就是说,两个运算分量必须全为真,结果才为真,否则结果为假。
(2)针对“||”运算符,有真出真,全假出假,也就是说,两个运算分量只要有真,结果就为真,只有两个运算分量全部为假,结果才为假。
(3)针对“!”运算符,只需要一个运算分量,这个逻辑运算符用于取反操作,也就是原来是真,使用了“!”后就变成假,原来是假,使用了“!”后就变成真。
这里有几点重要内容必须强调一下:
(1)千万不要把“&&”写成“&”,如果犯了这样的错误,则很可能程序不报错(因为“&”是位运算符),但得到的逻辑运算结果显然是不对的,同理,也千万不要把“||”写成“|”,同样,程序可能不报错(因为“|”也是位运算符),但得到的逻辑运算结果也是不对的。
(2)进行逻辑判断的时候,不等于0的数值全部都被认为是true,总结来说:由系统给出的逻辑运算结果不是0(假)就是1(真),不可能是其他数值,而在逻辑表达式中作为参加逻辑运算的对象,是0就表示假,非0就表示真。
上面的说法有一点不好理解。看看如下范例:
上面这行代码得到结果0,也就是假,4可以被认为是真,但是它却不等于true,true实际等于1。再看如下范例:
再看一例:
上面这行之所以结果为1(真),是因为前面曾说过,作为参加逻辑运算的对象,非0就表示真,所以这里4和5每一个都表示真,而根据表4.3,两个真值做“&&”运算结果仍然为真,也就是1。
2.逻辑运算符优先级问题
在一个逻辑表达式中,如果包含了多个逻辑运算符,例如:
该如何计算呢?此时,就必须再一次明晰运算符的运算优先级,这次明晰把逻辑运算符也增加进来,如表4.4所示。
表4.4 逻辑、算术、关系、赋值运算符优先级
从表4.4中可以看到,逻辑运算符中的“&&”和“||”优先级低于关系运算符,而逻辑运算符中的“!”优先级高于算术运算符,这一点请不要记错。而刚才提到的如下表达式:
建议尽量不要这样写,因为看起来很混乱,即使对照优先次序表仔细计算,也容易出错,当然非要进行计算也是可以的,因为表达式通常都是自左到右进行扫描计算的,所以,上述表达式与下述表达式等价:
当然,在日常书写代码中,尤其是配合后面会讲到的条件判断语句if时,会有一些常见的逻辑表达式写法,这些写法不必死记硬背,写多了自然也就记住了:
实际上逻辑运算符两侧的对象可以是任何类型数据,如字符型、实型甚至指针类型(后面章节会讲解)等。看看下面这行代码:
上面这种写法并不常见,逻辑运算符最常用的地方还是进行各种关系比较运算,如a>b&&c>d。例如,在角色扮演类网络游戏中,玩家的血大于0(玩家还活着)并且魔法大于0,那么玩家才可以使用魔法攻击敌人。
可以扩展一下表4.3,因为在逻辑判断的时候,非0值都看作真,0值都看作假,所以得到如表4.5所示的逻辑运算扩展真值表。
表4.5 逻辑运算扩展真值表
3.逻辑运算符求值问题
在逻辑表达式求解中,不是所有逻辑运算都会被执行,只有在必须执行下一个逻辑运算才能求出整个表达式的结果时,才执行该运算,这块的内容中包含面试陷阱,请注意。
(1)a&&b&&c
这个逻辑表达式中,根据“&&”全真出真,有假出假的规则,不难得到一个结论:只有a为真(非0)才需要判断b,只有a和b都为真才需要判断c,只要a为假,就不必判断b,如果a为真b为假,就不需要判断c。
看看如下范例,这里用if语句作为判断的条件(if语句后面会详细讲解)
再看一例:
该范例执行后,看一看a的值是多少,其实a的值是8,这是因为:3<5显然为真,所以会继续执行&&后面的内容也就是a=8(赋值语句),既然a=8得到了执行,那么8这个值便被赋给了a,所以最终a的值是8。
如果改造一下这个例子,把3<5修改为3>5看看会怎样:
该范例执行后,a的值是1,这是因为:3>5显然为假,&&的计算原则是有假出假,也就是说,系统根本不需要执行&&后面的a=8,就能判断出来整个逻辑表达式的结果为假,所以,a=8压根没被执行,最终a的值还是在定义时给的初始值1。
(2)a||b||c
这个逻辑表达式中,根据“||”有真出真,全假出假的规则,不难得到一个结论:只要a为真(非0)就不必判断b和c,只有a为假,才需要判断b,只有a和b都为假,才需要判断c。
看看如下范例,依旧用if语句作为判断的条件:
该范例执行后,看一看a的值是多少,其实a的值是8,这是因为:3>5显然为假,所以会继续执行“||”后面的a=8,既然a=8得到了执行,那么8这个值便被赋给了a,所以最终a的值是8。
如果改造一下这个例子,把3>5修改为3<5看看会怎样:
该范例执行后,a的值是1,这是因为:3<5显然为真,“||”的计算原则是有真出真,也就是说,系统根本不需要执行“||”后面的a=8,就能判断出来整个逻辑表达式的结果为真,所以,a=8压根没被执行,最终a的值还是在定义时给的初始值1。
从上述针对“&&”和“||”这两个逻辑运算符的求值举例中可以看到,很多逻辑表达式只需要计算其中的一部分内容,就可以得到整个逻辑表达式的值,这种逻辑表达式的求值特性,也称为逻辑表达式的“短路求值”特性(只要最终的结果已经可以确定是真或假,求值过程便宣告终止)。