6-8 逻辑运算符
关系(比较)运算符虽可用于检验特定条件真伪,但每次只能测试单一条件。而我们常需判断多个条件是否同时成立。例如核验彩票中奖时,必须比对所选所有号码是否与中奖号码完全匹配。在6选号码的彩票中,这需要进行6次比较,且所有条件都必须成立。在其他情况下,我们需要判断多个条件中是否存在任意一个成立。例如,若我们生病、过度疲惫,或在前述彩票场景中中奖,便可能决定今日缺勤。这涉及检查三个比较条件中是否存在任意一个成立。
逻辑运算符赋予了我们同时测试多个条件的能力。
C++ 提供三种逻辑运算符:
| Operator | Symbol | Example Usage | Operation |
|---|---|---|---|
| Logical NOT | ! |
!x |
true if x is false, or false if x is true |
| Logical AND | && |
x && y |
true if x and y are both true, false otherwise |
| Logical OR | || |
x || y |
true if either (or both) x or y are true, false otherwise |
逻辑非
你在第4.9节——布尔值中已经接触过逻辑非一元运算符。我们可以这样总结逻辑非的效果:
| Operator | Operand | Result |
|---|---|---|
! (Logical NOT) |
true |
false |
! (Logical NOT) |
false |
true |
若逻辑非运算的操作数评估为真,则逻辑非运算结果为假;若操作数评估为假,则结果为真。换言之,逻辑非运算将布尔值从真翻转为假,反之亦然。
逻辑非运算常用于条件语句中:
bool tooLarge { x > 100 }; // tooLarge is true if x > 100
if (!tooLarge)
// do something with x
else
// print an error
需要警惕的一点是,逻辑非运算符具有极高的运算优先级。新手程序员常会犯以下错误:
#include <iostream>
int main()
{
int x{ 5 };
int y{ 7 };
if (!x > y)
std::cout << x << " is not greater than " << y << '\n';
else
std::cout << x << " is greater than " << y << '\n';
return 0;
}
该程序输出:


但x并不大于y,这怎么可能?答案在于逻辑非运算符的优先级高于大于运算符,因此表达式!x > y实际会被解析为(!x) > y。由于x等于5,!x的值为0,而0 > y为假,故else语句得以执行!
上述代码片段的正确写法应为:
#include <iostream>
int main()
{
int x{ 5 };
int y{ 7 };
if (!(x > y))
std::cout << x << " is not greater than " << y << '\n';
else
std::cout << x << " is greater than " << y << '\n';
return 0;
}

这样,x > y 将先被求值,然后逻辑非运算符会翻转布尔结果。
最佳实践
若逻辑非运算符需作用于其他运算符的结果,则其他运算符及其操作数需用括号括起。
逻辑非运算符的简单用法(如 if (!value))无需括号,因为此时运算优先级不产生影响。
逻辑或
逻辑或运算符用于测试两个条件中是否至少有一个为真。若左操作数求值为真,或右操作数求值为真,或两者均为真,则逻辑或运算符返回真。否则返回假。
| Operator | Left operand | Right operand | Result |
|---|---|---|---|
|| (Logical OR) |
false |
false |
false |
|| (Logical OR) |
false |
true |
true |
|| (Logical OR) |
true |
false |
true |
|| (Logical OR) |
true |
true |
true |
例如,考虑以下程序:
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int value {};
std::cin >> value;
if (value == 0 || value == 1)
std::cout << "You picked 0 or 1\n";
else
std::cout << "You did not pick 0 or 1\n";
return 0;
}



在此情况下,我们使用逻辑或运算符来测试左侧条件(value == 0)或右侧条件(value == 1)是否为真。若任一(或两者)为真,逻辑或运算符将求值为真,这意味着if语句将执行。若两者皆为假,逻辑或运算符将求值为假,这意味着else语句将执行。
警告
新手程序员有时会尝试这样做:if (value == 0 || 1) // incorrect: if value is 0, or if 1当 1 被求值时,它将隐式转换为布尔值 true。因此该条件语句将始终评估为 true。
若需将变量与多个值进行比较,则需多次比较该变量:
if (value == 0 || value == 1) // correct: if value is 0, or if value is 1
你可以串联多个逻辑或语句:
if (value == 0 || value == 1 || value == 2 || value == 3)
std::cout << "You picked 0, 1, 2, or 3\n";
新手程序员有时会将逻辑或运算符(||)与位或运算符(|)混淆(详见第O.2课——位运算符)。尽管两者名称中都含有“或”字,但它们执行的是不同功能。混淆使用很可能导致错误结果。
逻辑与
逻辑与运算符用于测试两个操作数是否均为真。若两个操作数均为真,逻辑与返回真;否则返回假。
| Operator | Left operand | Right operand | Result |
|---|---|---|---|
&& (Logical AND) |
false |
false |
false |
&& (Logical AND) |
false |
true |
false |
&& (Logical AND) |
true |
false |
false |
&& (Logical AND) |
true |
true |
true |
例如,我们可能想知道变量x的值是否介于10和20之间。这实际上包含两个条件:我们需要知道x是否大于10,同时也要知道x是否小于20。
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int value {};
std::cin >> value;
if (value > 10 && value < 20)
std::cout << "Your value is between 10 and 20\n";
else
std::cout << "Your value is not between 10 and 20\n";
return 0;
}


在此情况下,我们使用逻辑与运算符来测试左侧条件(value > 10)与右侧条件(value < 20)是否同时为真。若两者均为真,逻辑与运算符将评估为真,此时if语句执行;若两者皆为假或仅有一者为真,逻辑与运算符将评估为假,此时else语句执行。
与逻辑或运算类似,你可以串联多个逻辑与语句:
if (value > 10 && value < 20 && value != 16)
// do something
else
// do something else
如果所有这些条件都为真,则执行if语句。如果其中任何一个条件为假,则执行else语句。
与逻辑与和位运算或类似,新手程序员有时会将逻辑与运算符(&&)与位运算与运算符(&)混淆。
短路求值
逻辑与运算符要返回真值,必须两个操作数都求值为真。若左操作数求值为假,逻辑与运算符会立即返回假值,无论右操作数求值结果为何。此时逻辑与运算符甚至不会求值右操作数!这种特性称为短路求值short circuit evaluation,主要用于优化计算效率。
同样地,若逻辑或运算的左操作数为真,则整个或条件必须求值为真,此时右操作数将不再被求值。
短路评估再次印证了为何不应在复合表达式中使用会产生副作用的运算符。请看以下代码片段:
if (x == 1 && ++y == 2)
// do something
如果 x 不等于 1,整个条件必然为假,因此 ++y 永远不会被求值!由此可见,y 仅在 x 评估为 1 时才会被递增,这很可能并非程序员的本意!
警告
短路求值可能导致逻辑或运算符和逻辑与运算符不求值右操作数。请避免将具有副作用的表达式与这些运算符结合使用。
关键要点
逻辑或与逻辑与运算符是运算数可任意顺序求值规则的例外,标准明确规定左侧运算数必须优先求值。
对于进阶读者
仅内置版本的运算符执行短路求值。若重载这些运算符使其适用于自定义类型,重载版本将不执行短路求值。
混合使用逻辑与和逻辑或
在同一表达式中混合使用逻辑与和逻辑或运算符往往难以避免,但这充满潜在风险。
由于逻辑与和逻辑或看似成对出现,许多程序员误以为它们具有相同的运算优先级(如同加减法与乘除法)。然而逻辑与运算符的优先级高于逻辑或运算符,因此逻辑与运算符将优先于逻辑或运算符进行求值(除非使用括号显式控制)。
新手程序员常会编写如下表达式:value1 || value2 && value3。由于逻辑与运算符优先级更高,该表达式将被解析为 value1 || (value2 && value3),而非 (value1 || value2) && value3。希望这正是程序员的本意!若程序员默认采用从左到右的运算顺序(如同加减法或乘除法),则会得到出乎意料的结果!
在同一表达式中混合使用逻辑与和逻辑或时,建议为每个运算符及其操作数显式添加括号。此举既能避免运算优先级错误,又可提升代码可读性,并清晰定义表达式的计算逻辑。例如:相较于直接书写 value1 && value2 || value3 && value4,更优的写法是 (value1 && value2) || (value3 && value4)。
最佳实践
当单个表达式中同时包含逻辑与和逻辑或运算时,请为每个运算符明确添加括号,确保其按预期方式求值。
德摩根定律
许多程序员常误以为 !(x && y) 等同于 !x && !y。遗憾的是,逻辑非运算符无法如此“分布”。
德摩根定律De Morgan’s laws揭示了逻辑非运算符在此类情况下的分配规则:
!(x && y) 等价于 !x || !y
!(x || y) 等价于 !x && !y
换言之,当分配逻辑非运算符时,需将逻辑与运算符转换为逻辑或运算符,反之亦然!
此规则在简化复杂表达式可读性时常能发挥作用。
对于进阶读者
我们可通过证明对于x和y的所有可能取值,皆有 !(x && y) 等于 !x || !y,从而验证德摩根定律的第一部分成立。为此,我们将运用一种名为真值表的数学概念:
x y !x !y !(x && y) !x || !y false false true true true true false true true false true true true false false true true true true true false false false false 在此表格中,第一列和第二列分别代表变量x和y。表格中的每行展示x和y可能取值的一种排列组合。由于x和y均为布尔值,仅需4行即可覆盖x和y所有可能取值的组合。
表格其余列表示基于x和y初始值需要求值的表达式。第三、第四列分别计算!x和!y的值,第五列计算!(x && y)的值,第六列则计算!x || !y的值。
你会发现每行中第五列的值都与第六列的值相匹配。这意味着对于x和y的所有可能值,!(x && y)的值都等于!x || !y——这正是我们要证明的结果!
德摩根定律的第二部分也可采用相同方法推导:
x y !x !y !(x || y) !x && !y false false true true true true false true true false false false true false false true false false true true false false false false 同样地,对于x和y的所有可能取值,我们都能发现!(x || y)的值等于!x && !y的值。因此,它们是等价的。
逻辑异或(XOR)运算符在哪里?
逻辑异或是一种在某些语言中提供的逻辑运算符,用于测试奇数个条件是否为真:
| Operator | Left operand | Right operand | Result |
|---|---|---|---|
| (Logical XOR) | false |
false |
false |
| (Logical XOR) | false |
true |
true |
| (Logical XOR) | true |
false |
true |
| (Logical XOR) | true |
true |
false |
C++ 并未提供显式的逻辑异或运算符(运算符^是位异或,而非逻辑异或)。与逻辑或或逻辑与不同,逻辑异或无法进行短路求值。正因如此,通过逻辑与和逻辑或运算符组合实现逻辑异或操作颇具挑战性。
然而,当操作数为布尔值时,运算符!= 产生的结果与逻辑异或相同:
| Left operand | Right operand | logical XOR | operator!= |
|---|---|---|---|
| false | false | false | false |
| false | true | true | true |
| true | false | true | true |
| true | true | false | false |
因此,逻辑异或运算可按以下方式实现:
if (a != b) ... // a XOR b, assuming a and b are bool
这可以扩展到多个操作数,如下所示:
if (a != b != c) ... // a XOR b XOR c, assuming a, b, and c are bool
若操作数(a、b 和 c)中有奇数个评估为真,则此表达式结果为真。
若操作数非布尔类型,使用不等号运算符 != 实现逻辑异或将无法达到预期效果。
对于高级读者
若需实现可处理非布尔运算数的逻辑异或运算,可将运算数静态转换为布尔类型:
if (static_cast<bool>(a) != static_cast<bool>(b) != static_cast<bool>(c)) ... // a XOR b XOR c, for any type that can be converted to bool然而,这段代码有些冗长。以下技巧同样有效,且更为简洁:
if (!!a != !!b != !!c) // a XOR b XOR c, for any type that can be converted to bool这利用了运算符!(逻辑非运算符)会隐式将操作数转换为布尔值的特性。然而,运算符!还会将布尔值从真反转为假,或反之。因此需要两次应用!运算符:首次执行隐式转换并反转布尔值,第二次则将布尔值恢复为原始状态。当多操作数异或运算的操作数个数为奇数时,这种双重反转是必要的,否则异或结果将被反转。
这两种操作都不够直观,若需使用请务必做好文档说明。
替代运算符表示法
C++ 中许多运算符(如 || 运算符)仅以符号形式命名。历史上,并非所有键盘和语言标准都支持输入这些运算符所需的全部符号。因此,C++ 为运算符提供了替代关键词集,使用单词而非符号表示。例如,可使用关键词 or 替代 ||。
完整列表可在此处查阅。其中以下三项尤为重要:
| Operator name | Keyword alternate name |
|---|---|
| && | and |
| ! | not |
这意味着以下内容是相同的:
std::cout << !a && (b || c);
std::cout << not a and (b or c);
虽然这些别名目前看起来可能更易理解,但大多数经验丰富的C++开发者更倾向于使用符号名称而非关键字名称。因此,我们建议学习并使用符号名称,因为这才是你在现有代码中常见的用法。
测验时间
问题 #1
求下列表达式的值。
注:在以下答案中,我们将通过展示求解步骤来“说明解题过程”。各步骤以 => 符号分隔。因短路规则被忽略的表达式将用方括号标注。例如
(1 < 2 || 3 != 3) =>
(true || [3 != 3]) =>
(true) =>
true
表示我们先计算 (1 < 2 || 3 != 3) 得出 (true || [3 != 3]),再计算该表达式得到“true”。由于短路评估,3 != 3 从未被执行。
a) (true && true) || false
显示答案
(true && true) || false =>
true || [false] =>
true
b) (false && true) || true
显示答案
(false && [true]) || true =>
false || true =>
true
短路运算仅在 || 的第一个操作数为真时发生。
c) (false && true) || false || true
显示答案
(false && [true]) || false || true =>
false || false || true =>
false || true =>
true
d) (5 > 6 || 4 > 3) && (7 > 8)
显示答案
(5 > 6 || 4 > 3) && (7 > 8) =>
(false || 4 > 3) && (7 > 8) =>
(false || true) && (7 > 8) =>
true && (7 > 8) =>
true && false =>
false
e) !(7 > 6 || 3 > 4)
显示答案
!(7 > 6 || 3 > 4) =>
!(true || [3 > 4]) =>
!true =>
false
问题 #2
在第 6.3 课——余数与幂运算中,我们编写了一个判断数字是否为偶数的函数,其代码如下:
#include <iostream>
bool isEven(int x)
{
// if x % 2 == 0, 2 divides evenly into our number, which means it must be an even number
return (x % 2) == 0;
}
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
if (isEven(x))
std::cout << x << " is even\n";
else
std::cout << x << " is odd\n";
return 0;
}
使用运算符operator!重写此函数,代替运算符operator==。
显示解决方案
#include <iostream>
bool isEven(int x)
{
return !(x % 2);
}
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
if (isEven(x))
std::cout << x << " is even\n";
else
std::cout << x << " is odd\n";
return 0;
}

浙公网安备 33010602011771号