8.3 布尔逻辑
前面我们学习了命题逻辑,其中在复合命题中,两个命题之间可通过联接词进行联接。对于这些联接词,我们演示了用文字和符号描述的两种方式:如“p∨q”也可描述为“p或q”。显然用符号描述更简洁,这种表示方式其实就是布尔逻辑的表式形式。
布尔逻辑得名于George Boole,他在19世纪中叶首次定义了逻辑的代数系统,称为逻辑代数或布尔代数。
与普通代数相似,布尔代数也使用字母来表示变量。但是,与普通代数不同的是,布尔代数的运算符、运算数更简单。在布尔代数中,变量的取值只能为“1”和“0”两种,表示两种逻辑状态,即“逻辑真”或“逻辑假”。注意这里的“1”和“0”没有数值大小的含义。
布尔代数提供了3种基本运算,分别是逻辑乘(“与”运算),逻辑加(“或”运算)和求反(“非”运算)。
8.3.1 逻辑或
在前面学习复合命题时有一个“或”的联接词,其实就是一种“逻辑或”运算。
假设有两个简单命题A、B,通过联接词“或”组合成复合命题,可表示为以下形式:

通常,逻辑变量用英文大写字母A、B、C……表示。
从表8-2所示的真值表中可看出,当命题A、B有一个是真命题时,复合命题就是真命题,只有当A、B两个命题都为假命题时,复合命题才是假命题。
如下所示,当用0和1来表示逻辑假和逻辑真时,“逻辑或”运算与代数中的加法运算相似:

因此,“逻辑或”运算又称为“逻辑加”运算。只有当两个逻辑变量的值都为0(逻辑假)时,“逻辑或”运算的结果才为0,其他情况下,“逻辑或”运算的结果都为1。
“逻辑或”运算用加号将两个逻辑变量连接起来,构成如下逻辑表达式:

根据“逻辑或”运算的规则,不管逻辑变量A的值为0还是1,将其与0、1、自身相加,可得出如下结果:

在程序语言中,两个逻辑变量进行“逻辑或”运算时可使用相应程序设计语言提供的“逻辑或”运算符进行操作。在C语言中,“逻辑或”运算符是“||”。
例如:编写一个C程序,判断用户输入的数据是否位于0~10之间。
如果要判断用户输入的数据是否位于0~10之间,由于数据边界有两处,则需要分两个分支进行判断。假设将用户输入的数据保存到变量x中,则需要用x>0和x<10这两个关系表达式进行判断,如图8-8所示。

图8-8
如图8-8所示,要判断数据x是否在0~10区间,需要进行多次逻辑判断:按从左向右的顺序来看,第一次需判断输入的数据是否小于0(x<0),若不小于0,说明输入的数据在图8-8所示的0坐标的右侧。接着进行第二次判断,判断x是否大于10(x>10),再根据判断情况得出结果。
根据以上思路编写的程序如下:

其实,对于这种情况我们可以使用逻辑运算符将两个或多个逻辑值进行联接,然后进行判断,这样可减少代码量,使程序更简洁。
如果使用“逻辑或”运算,可将以上程序改写为以下形式:

在上面的程序中,当x>10或者x<0时(两者满足其一),说明输入的数据未在0~10这个区间。反之,若两个条件都不满足,说明输入的数据在0~10这个区间。
8.3.2 逻辑与
在前面学习复合命题时有一个“且”的联接词,其实就是一种“逻辑与”运算。假设有2个简单命题A、B,通过联接词“且”组合成复合命题,可表示为以下形式:

从表8-1所示的真值表中可以看出,只有当命题A、B都是真命题时,复合命题才是真命题。
如下所示,当用0和1来表示逻辑假和逻辑真时,“逻辑与”运算与代数中的乘法运算相似:

因此,“逻辑与”运算又称为“逻辑乘”运算。只有当两个逻辑变量的值都为1(逻辑真)时,“逻辑与”运算的结果才为1。
“逻辑与”运算用乘号将两个逻辑变量连接起来,构成如下逻辑表达式:

根据“逻辑与”运算的规则,不管逻辑变量A的值为0还是为1,将其与0、1、自身进行“逻辑与”运算时,可得出如下结果:

在程序语言中,两个逻辑变量进行“逻辑与”运算时可使用相应程序设计语言提供的“逻辑与”运算符进行操作。在C语言中,“逻辑与”运算符是“&&”。
改写上例的程序,用“逻辑与”方式判断用户输入的数据是否位于0~10之间。这时,我们只需要用x>=0和x<=10来判断数据是否在0~10之间,而不用判断数据是否在0~10之外。因此,程序可改为如下形式:

8.3.3 逻辑非
逻辑非也称为逻辑反运算,就是对一个逻辑变量取相反的值,即原来为逻辑真,经过逻辑非运算后就变成逻辑假。反之,原来为逻辑假,经过逻辑非运算后就变成逻辑真了。
在前面学习复合命题联接词“非”时,用符号“¬p”表示对命题p进行“非p”操作。在逻辑表达式中也可这样书写,很多地方还可看到另外一种表示形式,即在变量上方加一条横线,为
的形式。
逻辑非表达式如下:

或

通常,称A为原变量,
为反变量,二者共同称为互补变量。
逻辑非运算的运算规则如下(真值表参见表8-3所示)。

也就是说“非0”为“1”,“非1”为0。根据以上规则可推出,“非(非0)”为“非1”(先计算括号中的“非0”),而“非1”为“0”,因此,“非非0”为“0”。据此可推出“非非A”为“A”。

可以将一次“非”运算看作一次对该变量的否定,则以上推理可理解为“双重否定表示肯定”。双重否定也可表示为如下形式:

根据“逻辑非”运算的规则,不管逻辑变量A的值为0还是为1,有如下运算结果:

在程序语言中,两个逻辑变量进行“逻辑非”运算时可使用相应程序设计语言提供的“逻辑非”运算符进行操作。在C语言中,“逻辑非”运算符是“!”,这个运算符能够实现对表达式的条件进行取反,若表达式值为true,则运算结果为false;若表达式值为false,则运算结果为true。
例如:
!(1<2)的运算结果为false。
!(2<1)的运算结果为true。
8.3.4 逻辑异或
除了上面介绍的3个基本逻辑运算操作之外,我们经常还会用到一个称为“逻辑异或”的运算。
逻辑异或运算用符号“⊕”连接两个逻辑变量,其表达式如下:

对于这个特别的逻辑运算,其运算规则是什么呢?
用文字来描述运算规则,当逻辑变量A、B的值不同时,异或运算的结果为真;反之,当逻辑变量A、B的值相同时,异或运算的结果为假。其真假表如表8-7所示。
表8-7 逻辑异或真值表

其实,“逻辑异或”运算可以转换为“逻辑与”、“逻辑或”、“逻辑非”这3种基本逻辑运算的组合:

根据表8-7所示的真值表,可推算出逻辑变量A与0、1、自身或自身取反的运算结果如下:

也就是说,逻辑变量A与0进行异或运算,结果仍为A;若A与1进行异或运算,结果为“非A”;若A与“非A”进行异或运算,结果为1;A与自身异或运算,结果为0。
在C语言中,只有二进制位运算才可以使用“逻辑异或”。
8.3.5 二进制位运算
讲到逻辑运算,不得不提一下程序设计中的二进制位运算。在C语言中,可以对整数、布尔类型和枚举类型进行二进制位运算。C语言中的位运算包括逻辑位运算和移位运算,我们这里学习的是逻辑运算,因此下面主要看一下二进制位的逻辑运算操作。
1.按位“与”运算
按位“与”运算符为“&”,其运算规则如下:

可以看出,其运算规则与“逻辑与”运算类似。不同的是,在运算时是将两个运算数据按位对齐,分别对各位进行运算,而不是将两个运算数据当成逻辑值来看待。
例如:计算3&5的结果。
要进行位运算,首先需要将两个数转换为二进制(根据计算机的位长决定位数),然后逐位对齐,再逐位进行“与”运算。

在上面逐位进行“与”运算时,只有当对应位都为1时才得1,否则结果都为0。因此,3&5的结果为1。
2.按位“或”运算
按位“或”运算符为“|”,其运算规则如下:

只有当两个二进制位均为0时,计算结果才为0;否则,结果均为1。
例如:计算3|5的结果。

在上面逐位进行“或”运算时,对应位中只要有1位为1,就得1,只有当对应位都为0时才得0。因此,3|5的结果为7。
3.按位取“反”运算
按位取“反”运算符为“~”,其运算规则如下:

可以看出,按位取“反”运算与“逻辑反”运算类似,当二进制位为0时,计算结果为1;当二进制位为1时,计算结果为0。
例如:计算~5的结果。

只看二进制位应该很好理解,取反运算将对应二进制位逐位取反(原来为1的变为0,原来为0的变为1)。
可是,为什么“1111 1010”表示十进制的-6呢?
这需要从计算机中数的表示说起。简单地说,在计算机中数据的机器码采用补码表示,其中最高位为符号位,当为1时表示这个数是负数,为0时表示这个数为正数。对于正数,其补码与原码相同;对于负数,其补码是原码按位取反再加上1。
上面取反计算的结果中,最高位取反后变为1,表示这个数为负数。因此,要知道其原码还需要进行转换。将补码再次按位取反再加上1就可以得原码(符号位不变),因此,可按以下方式转换得到原码:

可见,在计算机中,对数据进行取“反”的操作很多,仅在补码的转换中就会经常用到。
4.按位“异或”运算
按位“异或”运算符为“^”,其运算规则如下:

当两个二进制位相异(不相同)时,计算结果为1;当两个二进制位相同时,计算结果为0。
例如:计算3^5的结果。

在上面逐位进行“异或”运算时,对应位如果相同得0,对应位如果不相同得1。因此,3^5的结果为6。
