第 11 讲:运算符(三):比较、逻辑和条件运算符
下面我们介绍一下比较运算符、逻辑运算符和条件运算符。这三种运算符需要一起讲,因为它们之间是存在关联和关系的。
Part 1 六种比较运算符
先别被标题吓死了。六种比较运算符实际上就是我们经常在数学上使用的大于、大于等于、小于、小于等于、等于和不等于这六种比较符号。在 C# 里,和 C 语言一致,分别写成 >
、>=
、<
、<=
、==
和 !=
。特别需要说明的是,等号需要用两个等号表达,因为单个等号已经用来表达赋值过程了。为了避免赋值过程和比较过程使用同一个符号产生语义冲突,比较相等就多用了一次等号符号。
实际上也没什么好说的。它实际上就是在比较数据的大小。将比较结果(条件是否正确)以一个 bool
Part 2 八种逻辑运算符
C# 里有四种逻辑运算,分别是且(&&
)、或(||
)、贪婪且(&
)、贪婪或(|
)、且元(false
)、或元(true
)、异或(^
)和逻辑取反(!
)。我相信你肯定只知道逻辑且、或、非(逻辑取反),最多多一个逻辑异或运算。其它四个估计你完全不知道。这也没关系,我们慢慢来梳理它们。
2-1 逻辑且、或运算
我们使用 &&
连接两个 bool
类型的变量,用来表达和表示两个条件必须都成立的时候,才成立。
这个 condition
的值是 true
还是 false
呢?显然是 true
,因为两个条件 a
和 b
全都是正确的表达式,因此相当于在算 true && true
,那当然结果就是 true
了。
||
和 &&
显然,两个都是 true
的结果。只有两个都是 false
的时候,结果才是 false
。因此这个例子的 condition
结果依旧是 true
2-2 贪婪逻辑运算以及短路现象
要说清楚贪婪逻辑运算,必须要先说一下逻辑且和逻辑或本身存在的一种短路现象,它分逻辑且和逻辑或两种运算来解释:
逻辑且运算(
&&
):由于两个条件里,有一个为false
的时候,就已经可以确定表达式结果,因此如果当左侧结果就是false
的时候,右侧结果不论是普通表达式,还是带有运算过程的表达式,都不会被得到执行;逻辑或运算(
||
):由于两个条件里,有一个为true
的时候,就已经可以确定表达式结果,因此如果当左侧结果就是true
的时候,右侧结果不论是普通表达式,还是带有运算过程的表达式,都不会被得到执行。
可以从内容里看出,它们的差距就在 true
和 false
这个词不一样。下面我们来说一下这个到底是什么意思。
可以看到,其实两个例子的结果分别是 0, 4, true
和 0, 3, true
。非贪婪运算在计算 --a <= 0
的时候,发现 --
是前缀运算,因此先把 a
--a
的结果就是此时 a
的数值(即 0);最后,--a <= 0
就一定是成立的,因此整个表达式的结果是 true
。
因为使用的是逻辑或运算,因此左侧条件已经可以确定整个表达式 --a <= 0 || --b <= 0
的结果了,那么,--b <= 0
就不必计算了,即使 --b
会影响 b
的结果,此时的 b
照样不会减去 1 个单位,因此 a
变成 0,b
没有任何改动,且最终的结果是 true
。
贪婪逻辑或运算说白了就是没有短路现象的逻辑或运算。不管左边是不是能确定表达式结果,右侧的表达式依旧需要运算。那么,结果自然就是 0, 3, true
了(虽然我们知道,就算 b
变成 3,--b <= 0
也是 false
的;但是因为 true
或一个不管什么东西的结果都是 true
)。
同样地,逻辑且和贪婪逻辑且运算是一样的道理。
思考一下,这个例子里的结果分别是多少,以及把贪婪逻辑且运算改成逻辑且运算后,结果又是多少。
2-3 逻辑异或运算
异或运算类似于我们逻辑上理解的“要么……要么……”。如果别人告诉你,要么苹果要么香蕉的时候,如果你都选择了,或者都不要的话,这两种情况都是不成立的。因为要么表示里面的东西你必须选,但是只能选一个。异或运算就是这么一个逻辑。
由于 a > 10
的结果是 false
,而 b >= 3
的结果是 true
。按照逻辑异或运算的规则,因为有且仅有一个表达式结果为 ,因此 condition
的结果就是 false ^ true = true
。
最后,我们还剩下逻辑元运算两个(
true
和false
)。这一点我们先放一边,我们先讲条件运算。
2-4 逻辑取反运算
逻辑取反运算比起前面的就要简单很多了。逻辑取反就是把对改成错、把错改成对的过程。
如果 a > b
条件为真(即 true
的话),那么 condition
的结果就为 false
。
请注意,!
运算符仅用来表达一个 bool
结果的取反,因此必须要在表达式整体上打括号后,然后前面追加 !
。如果写成 !a > b
的话,C# 会产生一个编译器错误,告诉你 !a
a
是一个数。
Part 3 条件运算符
条件运算符(Conditional Operator)是一个需要三个表达式才能操作的运算符,它用 ?
和 :
作为分隔,问号的前面写条件(一个 bool
类型的变量),然后 ?
和 :
中间写这个条件成立的时候的数值,:
后写条件不成立的时候的数值。两个数值必须是同一种数据类型的。
举个例子:
这表示,当 a > 10
c
的数值,否则 c
为 40。
Part 4 逻辑元运算
坐稳了。这一节的难度有点大,而且很考察各位的逻辑推理能力。千万不要掉以轻心。
逻辑元运算(Meta Logical Operator)一共有两个(true
和 false
)。不要以为 true
和 false
只能表示一个条件为真和假,它还有别的意思:当成一个运算符,记作 true(表达式)
和 false(表达式)
。
逻辑元运算将 &&
和 &
,还有 ||
和 |
关联起来。公式大概是这样的:
a && b
等价于false(a) ? a : a & b
;a || b
等价于true(a) ? a : a | b
。
比如 a && b
。我们先运算 false(a)
的结果,如果这个结果是 true
,我们就直接把 a
作为 a && b
的结果;否则,就取 a & b
整个表达式的结果。
不好理解?行,我们拿驾车的例子给大家介绍一下逻辑元运算。假设我们开着一辆车向前行驶。遇到红绿灯路口需要过马路。那么,如果我们将车辆油箱里的油还剩下多少分成三种状态:
红色:没油了;
黄色:有一点油,但是要提出警告,因为可能过不了马路;
绿色:有充足的油前进。
我们把红绿灯的状态也表示成三种情况:
红色:红灯无法前进;
黄色:黄灯;
绿色:绿灯可以前进。
那么,假设我们把“带有红色、黄色、绿色”这三种状态的逻辑称为“状态逻辑”。那么,状态逻辑的运算应该如何呢?
如果两个条件里只要有一个是红色的话,我们就断定结果状态一定是红色的;
如果两个条件里两个都是黄色的话,我们也认为最终状态也是红色的;
如果两个条件有一个黄色的话,我们就认为结果状态是黄色的;
如果前面都不成立,则现在只剩下绿色了,因此我们认为其它情况下的结果状态都是绿色的。
那么,我们可以具象化逻辑:定义一个“状态逻辑”的变量 canGo
,表示是否可以前行。它取决于“油箱是否有油”和“前面红绿灯是否可以允许前进”两个因素,因此我们需要把这两个条件用逻辑且连接起来:
其中,
fuelStatus
是油箱有没有油的状态(有油,有油但是少、没有油);signalStatus
是红绿灯是否允许我们前进的状态(红灯、黄灯和绿灯)。
true
和 false
这两个逻辑元运算的过程了。
true(状态)
:状态是绿色的时候,结果为true
,否则为false
;false(状态)
:状态是黄色或红色的时候,结果为true
,否则为false
。
很好。我们最后带入公式,看看是否逻辑正常:fuelStatus && signalStatus
用逻辑元运算来表达的话,可以展开成 false(fuelStatus) ? fuelStatus : fuelStatus & signalStatus
。
这个逻辑就很好理解了。如果油箱状态是红色(或黄色)的时候,我们就可以直接认为 canGo
直接是红色(或黄色)了:因为既然油箱都没油了,那我们自然就不可能前进(这一点就是表达式里体现到的短路现象)。但是,如果油箱的油还多的话,我们这个时候就肯定要看红绿灯是否允许我们前行,因此,我们还要看 signalStatus
的结果才行。
那么,为什么不是直接看 signalStatus
的状态作为结果,而是 fuelStatus & signalStatus
呢?因为,我们不能仅通过信号灯的状态就确定我们是否可以继续通行,因此需要看的是油箱和信号灯两个状态才可以断言结果。你换个角度想一想 bool
里 &&
和 &
,就可以想通这个道理了。
这就称为逻辑元运算。逻辑元运算决定了条件是否成立的底层逻辑,而 &&
和 ||
可以认为是用贪婪运算和逻辑元运算共同构成的一个复杂表达式。
逻辑元运算是唯一一个无法直接使用的运算符;换句话说,你无法使用代码书写的方式来使用这个运算符。比如说你这么写代码:
,这个写法就是不行的。它的出现实际上是为了辅助false(condition)
&
运算符(贪婪逻辑且运算)和|
运算符(贪婪逻辑或运算)提供帮助,构成&&
和||
的中间运算过程。正是因为它是中间运算过程,因此难度比套用公式还要难理解一些。
Part 5 总结
本节内容难度可能有点大,考察各位对逻辑的理解。比较运算其实没有什么可以说的,主要就是逻辑运算比较不好理解,短路现象、元运算等等。