6-6 条件运算符
| Operator | Symbol | Form | Meaning |
|---|---|---|---|
| Conditional | ?: | c ? x : y | If conditional c is true then evaluate x, otherwise evaluate y |
条件运算符conditional operator ( ?: )(有时也称为算术条件conditional operator运算符) 是一种三元运算符(即接受三个操作数的运算符)。由于历史上它曾是 C++ 中唯一的此类运算符,因此也常被称为“三元运算符”。
?: 运算符为实现特定类型的 if-else 语句提供了简写方法。
相关内容
我们在第4.10课——if语句介绍中讲解了if-else语句。
回顾一下,if-else语句采用以下形式:
if (condition)
statement1;
else
statement2;
如果条件(condition)求值为真(true),则执行语句(statement)1;否则执行语句(statement)2。else和语句2均为可选项。
?: 运算符采用以下形式:
condition ? expression1 : expression2;
如果条件(condition)求值为真(true),则执行表达式(expression)1;否则执行表达式(expression)2。冒号和表达式2不可省略。
考虑如下形式的if-else语句:
if (x > y)
max = x;
else
max = y;
这可以改写为:
max = ((x > y) ? x : y);
在这种情况下,条件运算符可以帮助紧凑代码而不损失可读性。
一个示例
请考虑以下示例:
#include <iostream>
int getValue()
{
std::cout << "Enter a number: ";
int x{};
std::cin >> x;
return x;
}
int main()
{
int x { getValue() };
int y { getValue() };
int max { (x > y) ? x : y };
std::cout << "The max of " << x <<" and " << y << " is " << max << ".\n";
return 0;
}
首先,让我们输入5和7作为参数(即x为5,y为7)。当max初始化时,表达式(5 > 7) ? 5 : 7将被求值。由于5 > 7为假,因此得到false ? 5 : 7,其结果为7。程序输出:

现在输入7和5作为参数(即x为7,y为5)。此时得到(7 > 5) ? 7 : 5,该表达式为真,因此执行7 : 5运算,结果为7。程序输出:

条件运算符作为表达式的一部分进行求值
由于条件运算符作为表达式的一部分进行求值,因此它可以在任何接受表达式的位置使用。当条件运算符的操作数是常量表达式时,该运算符可用于常量表达式中。
这使得条件运算符能在无法使用语句的位置发挥作用。
例如在初始化变量时:
#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替代方案。
你可能会尝试类似这样的方法:
#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 << '\n'; // Compile error: classSize not defined
return 0;
}

然而,这段代码无法编译,你会收到一条错误信息,提示 classSize 未被定义。这类似于函数内部定义的变量在函数结束时失效的情况——在 if 语句或 else 语句内部定义的变量,会在该语句结束时失效。因此,当我们尝试打印 classSize 时,它早已被销毁。
若需使用if-else结构,应采用如下写法:
#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 << '\n';
return 0;
}

这个方法之所以可行,是因为 getClassSize(false) 是一个表达式,而 if-else 逻辑位于函数内部(函数中可以使用语句)。但这会产生大量冗余代码,而我们本可以直接使用条件运算符来实现相同效果。
括号化条件运算符
由于C++将大多数运算符的优先级置于条件运算符之上,因此使用条件运算符编写表达式时,很容易出现计算结果与预期不符的情况。
相关内容
我们在后续的第6.1节——运算符优先级与结合性中,将详细讲解C++如何确定运算符的计算优先级。
例如:
#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)。
下面是另一个常见错误的示例:
#include <iostream>
int main()
{
int x { 2 };
std::cout << (x < 0) ? "negative" : "non-negative";
return 0;
}


你可能会认为这段代码会输出非负数,但实际上它会输出0。
可选阅读
以上示例的运行过程如下:首先,x < 0 评估为 false。此时部分求值的表达式变为 std::cout << false ? “negative” : “non-negative”。由于运算符 << 的优先级高于运算符 ?,该表达式将按 (std::cout << false) ? ‘negative’ : “non-negative” 的形式进行求值。因此执行 std::cout << false,输出 0(并返回 std::cout)。
此时部分求值后的表达式变为 std::cout ? “negative” : “non-negative”。由于条件中仅剩 std::cout,编译器将尝试将其转换为 bool 类型以求解条件。令人意外的是,std::cout 存在定义的 bool 类型转换,该转换极可能返回 false。假设返回 false,此时表达式变为 false ? “negative” : “non-negative”,最终求值为 “non-negative”。因此完全求值后的语句为 “non-negative”;。仅包含字面量(此处为字符串字面量)的表达式语句不产生任何效果,至此处理完成。
为避免此类运算优先级问题,条件运算符应按以下方式添加括号:
- 在复合表达式(含其他运算符的表达式)中使用时,需将整个条件运算(含操作数)括起来。
- 为提高可读性,若条件包含任何运算符(除函数调用运算符外),建议添加括号。
条件运算符的操作数无需添加括号。
下面通过实例说明含条件运算符的语句应如何添加括号:
return isStunned ? 0 : movesLeft; // not used in compound expression, condition contains no operators
int z { (x > y) ? x : y }; // not used in compound expression, condition contains operators
std::cout << (isAfternoon() ? "PM" : "AM"); // used in compound expression, condition contains no operators (function call operator excluded)
std::cout << ((x > y) ? x : y); // used in compound expression, condition contains operators
最佳实践
在复合表达式中使用条件运算时,应将整个条件运算(包括操作数)用括号括起。
为提高可读性,若条件包含任何运算符(函数调用运算符除外),建议将其括起。
表达式的类型必须匹配或可转换
为符合C++的类型检查规则,必须满足以下条件之一:
- 第二和第三操作数的类型必须匹配。
- 编译器必须能够将第二和第三操作数中的一项或两项转换为匹配类型。编译器使用的转换规则相当复杂,某些情况下可能产生意外结果。
对于进阶读者
另有一种情况:允许第二个和第三个操作数中至少一个为抛出表达式。抛出机制将在第27.2节——基础异常处理中详述。
例如:
#include <iostream>
int main()
{
std::cout << (true ? 1 : 2) << '\n'; // okay: both operands have matching type int
std::cout << (false ? 1 : 2.2) << '\n'; // okay: int value 1 converted to double
std::cout << (true ? -1 : 2u) << '\n'; // surprising result: -1 converted to unsigned int, result out of range
return 0;
}
假设使用4字节整数,上述代码将输出:


通常情况下,可以混合使用基本类型的操作数(但不能混合使用有符号和无符号值)。如果任一操作数不是基本类型,通常最好手动将其中一个或两个操作数显式转换为匹配类型,这样就能准确知道结果。
相关内容
上述涉及有符号与无符号值混合的意外情况源于算术转换规则,该规则将在第10.5节——算术转换中详细说明。
若编译器无法将第二个和第三个操作数转换为匹配类型,将导致编译错误:
#include <iostream>
int main()
{
constexpr int x{ 5 };
std::cout << ((x != 5) ? x : "x is 5"); // compile error: compiler can't find common type for constexpr int and C-style string literal
return 0;
}

在上例中,其中一个表达式是整数,另一个是C风格字符串常量。编译器无法自行找到匹配类型,因此会导致编译错误。
这种情况下,您可以进行显式转换,或使用if-else语句:
#include <iostream>
#include <string>
int main()
{
int x{ 5 }; // intentionally non-constexpr for this example
// We can explicitly convert the types to match
std::cout << ((x != 5) ? std::to_string(x) : std::string{"x is 5"}) << '\n';
// Or use an if-else statement
if (x != 5)
std::cout << x << '\n';
else
std::cout << "x is 5" << '\n';
return 0;
}

对于高级读者
若 x 为 constexpr,则条件 x != 5 属于常量表达式。此类情况下,应优先使用 if constexpr 而非 if,编译器可能会生成相应警告(若将警告视为错误,则该警告将升级为错误)。
由于我们尚未讲解 if constexpr(将在第 8.4 节——常量表达式 if 语句中介绍),本例中 x 故意设为非常量表达式,以避免潜在的编译器警告。
那么何时应该使用条件运算符?
当执行以下操作时,条件运算符最为实用:
- 用两个值中的一个初始化对象。
- 将两个值中的一个赋值给对象。
- 向函数传递两个值中的一个。
- 从函数返回两个值中的一个。
- 输出两个值中的一个。
复杂表达式通常应避免使用条件运算符,因为它们往往容易出错且难以阅读。
最佳实践
在复杂表达式中应尽量避免使用条件运算符。

浙公网安备 33010602011771号