条件运算符
本节阅读量:
| 符号 |
形式 |
含义 |
| ? : |
c ? x : y |
表达式c的结果为true,计算x,否则计算y |
条件运算符(? :)(有时也称为算术 if 运算符)是一个三元运算符(即接受 3 个操作数的运算符)。由于它在历史上一直是 C++ 中唯一的三元运算符,因此有时也被直接称为“三元运算符”。
条件运算符为某些特定形式的 if-else 语句提供了一种简洁的书写方式。
概括来说,if-else 语句的形式如下:
1
2
3
4
|
if (条件表达式)
true_对应的语句;
else
false_对应的语句;
|
如果条件表达式的结果为 true,则执行 true_对应的语句,否则执行 false_对应的语句。其中 else 及 false_对应的语句 是可选的。
而条件运算符的形式如下:
1
|
条件表达式 ? true_对应的语句 : false_对应的语句;
|
如果条件表达式的结果为 true,则执行 true_对应的语句,否则执行 false_对应的语句。与 if-else 不同的是,这里的 : 和 false_对应的语句 是必选的。
考虑下面这个 if-else 语句:
1
2
3
4
|
if (x > y)
greater = x;
else
greater = y;
|
可以改写为:
1
|
greater = ((x > y) ? x : y);
|
在这种情况下,条件运算符可以在不损失可读性的前提下压缩代码。
条件运算符是一个表达式
由于条件运算符本身是一个表达式而不是语句,因此它可以用在任何需要表达式的地方。
例如,初始化变量时:
1
2
3
4
5
6
7
8
9
10
|
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
constexpr int classSize { inBigClassroom ? 30 : 20 };
std::cout << "The class size is: " << classSize << '\n';
return 0;
}
|
如果改用 if-else 来完成,你可能会尝试这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
if (inBigClassroom)
constexpr int classSize { 30 };
else
constexpr int classSize { 20 };
std::cout << "The class size is: " << classSize;
return 0;
}
|
然而,这段代码无法通过编译,错误提示是 classSize 未定义。就像函数中定义的变量会在函数结束时销毁一样,在 if 或 else 语句块中定义的变量也会在对应语句块结束时销毁。因此,当我们尝试打印 classSize 时,它实际上已经不存在了。
如果确实想使用 if-else,则必须写成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <iostream>
int getClassSize(bool inBigClassroom)
{
if (inBigClassroom)
return 30;
else
return 20;
}
int main()
{
const int classSize { getClassSize(false) };
std::cout << "The class size is: " << classSize;
return 0;
}
|
这段代码可以正常工作,因为 getClassSize(false) 本身是一个表达式,而 if-else 逻辑被放在函数内部(在函数体中可以使用语句)。但缺点显而易见:需要多写不少额外的代码。
用圆括号包裹条件运算符
由于 C++ 中大多数运算符的优先级都高于条件运算符,因此条件运算符的求值顺序很容易与你的预期不一致。
例如:
1
2
3
4
5
6
7
8
9
10
11
|
#include <iostream>
int main()
{
int x { 2 };
int y { 1 };
int z { 10 - x > y ? x : y };
std::cout << z;
return 0;
}
|
你可能期望它按 10 - (x > y ? x : y) 的方式求值(结果为 8),但它实际上是按 (10 - x) > y ? x : y 的方式求值的(结果为 2)。
鉴于此,建议使用括号将条件运算符包裹起来:
- 如果条件运算符作为子表达式使用,应用圆括号将整个条件运算符括起来。
- 为了提高可读性,若条件表达式中含有任何运算符(函数调用运算符除外),也应用圆括号将其括起来。
条件运算符的操作数本身则不需要加括号。
让我们来看几个包含条件运算符的语句,看看该如何加括号:
1
2
3
4
|
return isStunned ? 0 : movesLeft; // 不作为子表达式, 条件表达式中无操作符
int z { (x > y) ? x : y }; // 不作为子表达式, 条件表达式中有操作符
std::cout << (isAfternoon() ? "PM" : "AM"); // 作为子表达式, 条件表达式中无操作符 (函数调用运算符除外)
std::cout << ((x > y) ? x : y); // 作为子表达式, 条件表达式中有操作符
|
相关内容
在后续的——运算符优先级和结合性——一节中,我们将讨论 C++ 对运算符求值顺序进行优先级排序的规则。
最佳实践
当条件运算符用于复合表达式中时,请用圆括号将整个条件运算符括起来。
为了提高可读性,若条件表达式中包含任何运算符(函数调用运算符除外),请用括号将其括起来。
表达式的类型必须匹配,或可以相互转换
为了符合 C++ 的类型检查规则,必须满足以下条件之一:
- 第二个和第三个操作数的类型相同。
- 编译器能找到一种方式,将第二个和第三个操作数中的一个或两个转换为一致的类型。编译器所使用的转换规则比较复杂,在某些情况下可能会产生意外的结果。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
int main()
{
std::cout << (true ? 1 : 2) << '\n'; // okay: 两个操作数的类型都是 int
std::cout << (false ? 1 : 2.2) << '\n'; // okay: int 1 被转换为 double
std::cout << (true ? -1 : 2u) << '\n'; // 令人意外: -1 被转换为 unsigned int, 发生溢出
return 0;
}
|
以上代码输出如下:
通常情况下,基本类型之间的混用(不包括混用有符号与无符号类型)是可以的。如果其中一个操作数并非基本类型,通常最好自行显式地将其转换为匹配的类型,这样就能清楚地知道最终得到的是什么。
如果编译器无法找到一种方式将第二个和第三个操作数转换到匹配的类型,就会导致编译错误:
1
2
3
4
5
6
7
8
9
|
#include <iostream>
int main()
{
constexpr int x{ 5 };
std::cout << (x != 5 ? x : "x is 5"); // 编译错误: constexpr int 与 C 风格字符串的类型无法匹配
return 0;
}
|
在上面的例子中,一个操作数是整数,另一个是 C 风格的字符串字面值。编译器无法找到匹配的类型,因此会产生编译错误。
在这种情况下,可以进行显式的类型转换,或者改用 if-else 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#include <iostream>
#include <string>
int main()
{
constexpr int x{ 5 };
// 可以显式地做类型转换
std::cout << (x != 5 ? std::to_string(x) : std::string{"x is 5"}) << '\n';
// 或者改用 if-else
if (x != 5)
std::cout << x << '\n';
else
std::cout << "x is 5" << '\n';
return 0;
}
|
那么什么时候应该使用条件运算符?
条件运算符适合用在以下几种场景:
- 用两个值之一来初始化对象。
- 将两个值之一赋给对象。
- 将两个值之一作为参数传递给函数。
- 从函数中返回两个值之一。
- 打印两个值之一。
对于复杂的表达式,应尽量避免使用条件运算符,因为这样的用法往往容易出错,且不易阅读。
最佳实践
在复杂的表达式中,应尽量避免使用条件运算符。
5.4 数字系统(十进制、二进制、十六进制和八进制)
上一节