运算符名 | 语法 | 可重载 | 原型示例(对于 class T) | |
---|---|---|---|---|
类内定义 | 类外定义 | |||
函数调用 | a(a1, a2) | 是 | R T::operator()(Arg1 &a1, Arg2 &a2, … …); | N/A |
逗号 | a, b | 是 | T2& T::operator,(T2 &b); | T2& operator,(const T &a, T2 &b); |
条件 | a ? b : c | 否 | N/A | N/A |
解释
函数调用运算符为任何对象提供函数语义。
条件运算符(通俗地称为“三元条件”)检查第一表达式的布尔值,然后根据其结果值,求值并返回第二或第三表达式。
内建的函数调用运算符
函数调用表达式的形式为
E ( A1, A2, A3,… ) | ||
其中
- E 是指名函数的表达式
- A1, A2, A3,… 是可能为空的任意表达式的列表,但为避免歧义不允许顶层出现逗号运算符。
指名函数的表达式可以为
a) 指代函数的左值表达式
b) 函数指针
c) 选择成员函数的显式类成员访问表达式
d) 隐式类成员访问表达式,例如在另一成员函数内使用的成员函数名。
由 E
所指代的函数(或成员)名可以是重载的,用重载决议规则决定要调用哪个重载。
若 E
指定成员函数,则它可为虚,这种情况下将以运行时的动态派发调用该函数的最终覆盖函数。
为调用该函数,
以任意顺序对表达式 E 和作为实参所提供的所有表达式 A1 、A2 、A3 等进行求值,它们互相是无顺序的(unsequenced)。 | (C++17 前) |
表达式 E 按顺序早于(sequenced before)表达式 A1 、A2 、A3 以及各默认实参(若存在)。以任意顺序求值各实参表达式,它们互相是顺序不确定的(indeterminately sequenced)。 | (C++17 起) |
各个函数形参以其对应实参(若需要,经过隐式转换后)初始化。若没有对应实参,则使用对应的默认实参,而若其不存在则程序非良构。如果所进行的是成员函数调用,则将指向当前对象的 this 指针,如同使用显式转型一般,转换到函数所期待的 this 指针。各个形参的初始化和销毁是在调用方的语境中进行的,这意味着,例如当某个形参的构造函数抛出异常时,不会考虑定义于函数内的异常处理块,即使是函数 try 块也如此。若函数为变参函数,则对省略号形参所匹配的所有实参实施默认实参提升。形参的生存期是结束于定义它的函数返回时,还是外围全表达式的结尾,是由实现定义的。
函数调用表达式的返回类型是被选择函数的返回类型,以静态绑定决定(忽略 virtual
关键词),即使实际调用的覆盖函数返回不同的类型。这允许覆盖函数返回引用或指针,指向派生于基类函数所返回类型的类,即 C++ 支持协变(covariant)返回类型。若 E
指定析构函数,则返回类型为 void。
当一个类类型 X 的对象被传递给函数,或被从函数返回时,如果 X 的每个复制构造函数、移动构造函数和析构函数均为平凡或被弃置(delete)的,并且 X 拥有至少一个未被弃置的复制或移动构造函数,则容许实现创建保有函数形参或结果对象的临时对象。 临时对象分别从函数实参或返回值构造,而函数的形参或返回对象的初始化,如同使用未被弃置的平凡构造函数对临时对象进行复制一样进行(即使该构造函数无法访问,或它并不会被进行对象的复制或移动时的重载决议所选择,也是如此)。 这允许小的类类型(如 std::complex 或 std::span ),可以在寄存器中传递给函数或从函数返回。 | (C++17 起) |
若函数返回左值引用或到函数的右值引用,则函数调用表达式的值类别为左值,若函数返回到对象的右值引用,则值类别为亡值,否则值类别为纯右值。若函数调用表达式为对象类型的纯右值,则它必须拥有完整对象类型,除非该纯右值不会被实质化,比如 (C++17 起)用作 decltype 的操作数,或用作作为 decltype
操作数的内建逗号运算符表达式的右操作数。
函数调用表达式在语法上与值初始化 T(),函数风格转型表达式 T(A1),以及临时量的直接初始化 T(A1, A2, A3, …) 相似,其中 T
是一个类型的名称。
运行此代码
- #include <cstdio>
- struct S
- {
- int f1(double d) {
- return printf("%f \n", d); // 变参函数调用
- }
- int f2() {
- return f1(7); // 成员函数调用,同 this->f1()
- // 整数形参转换为 double
- }
- };
- void f() {
- puts("function called"); // 函数调用
- }
- int main()
- {
- f(); // 函数调用
- S s;
- s.f2(); // 成员函数调用
- }
输出:
- function called
- 7.000000
内建的逗号运算符
逗号运算符表达式的形式为
E1 , E2 | ||
逗号表达式 E1, E2 中,对 E1
求值并舍弃其结果(尽管当它具有类类型时,直到包含它的全表达式的结尾之前都不会销毁它),其副作用在表达式 E2
的求值开始前完成(注意,用户定义的 operator,
不能保证定序) (C++17 前)。
逗号表达式结果的类型、值和值类别严格为其第二操作数 E2
的类型、值和值类别。若 E2
为临时量表达式 (C++17 起),则表达式的结果为该临时量表达式 (C++17 起)。若 E2
为位域,则结果为位域。
各种逗号分隔列表,例如函数实参列表(f(a, b, c))和初始化器列表 int a[] = {1,2,3},其中的逗号都不是逗号运算符。若需要在这种语境中使用逗号运算符,则必须加括号:f(a, (n++, n+b), c)。
以逗号表达式为下标表达式是被弃用的。 例如, a[b, c] 被弃用而 a[(b, c)] 未被弃用。 | (C++20 起) |
运行此代码
输出:
- n = 2
- m = 7
条件运算符
条件运算符表达式的形式为
E1 ? E2 : E3 | ||
对条件运算符的第一操作数求值并将其按语境转换为 bool。当第一操作数的值计算和所有副作用完成之后,若结果为 true,则求值第二操作数。若结果为 false,则求值第三操作数。
条件表达式 E1 ? E2 : E3 的类型和值类别按照下列规则确定:
1) 若 E2
或 E3
具有 void 类型,则下列之一必须为真,否则程序非良构:
1.1) E2
或 E3
(但非两者)为(可带括号的)throw 表达式。条件运算符的结果具有另一表达式的类型和值类别。若另一表达式为位域,则结果为位域。这种条件运算符常用于 C++14 之前的 C++11 constexpr 编程。
- std::string str = 2+2==4 ? "ok" : throw std::logic_error("2+2 != 4");
1.2) E2
和 E3
都具有 void 类型(包括两者均为 throw 表达式的情况)。结果为 void 类型的纯右值。
- 2+2==4 ? throw 123 : throw 456;
2) 否则,若 E2 或 E3 均为值类别相同的泛左值位域,类型分别为 cv1 T 和 cv2 T,则此节剩下的部分中认为个操作数都拥有 cv T 类型,其中 cv 是 cv1 与 cv2 的合并。 | (C++14 起) |
3) 否则,若 E2
与 E3
拥有不同类型且至少有一个是(可有 cv 限定的)类类型,或都是同一值类别的泛左值且具有除了 cv 限定性之外都相同的类型,则尝试组成隐式转换序列(其中忽略成员访问,操作数是否为位域,或转换函数是否被弃置) (C++14 起),从每个操作数转换到由另一操作数所确定的目标类型,如下文所述。一个名为 X
的 TX
类型的操作数,能以如下方式转换成另一名为 Y
的 TY
类型操作数的目标类型:
3.1) 若 Y
为左值,则目标类型为 TY&
,且引用必须直接绑定到左值;
3.2) 若 Y
为亡值,则目标类型为 TY&&
,且引用必须直接绑定;
3.3) 若 Y
为纯右值,或若两个转换序列都无法组成,且 TX
和 TY
至少有一个是(可为 cv 限定的)类类型,
3.3.1) 若 TX
和 TY
(忽略 cv 限定性)为相同类类型,且 TY
至少有 TX
的 cv 限定,则目标类型为 TY
,
3.3.2) 否则,若 TY
是 TX
的基类,则目标类型为带有 TX
的 cv 限定符的 TY
- struct A {};
- struct B : A {};
- using T = const B;
- A a = true ? A() : T(); // Y = A(), TY = A, X = T(), TX = const B. 目标类型 = const A
3.3.3) 否则,目标类型为 Y
在应用左值到右值、数组到指针和函数到指针标准转换后会有的类型。
3.4) 若两个序列(E2 到 E3 的目标类型和 E3 到 E2 的目标类型)均可组成,或只能组成一个但它是有歧义转换序列,则程序非良构。
3.5) 若恰能组成一个转换序列(注意它仍然可能非良构,例如由于访问违规),则应用该转换序列,此描述的余下部分(从 (4) 之后)中用转换后的操作数取代原操作数。
3.6) 若不能组成任何转换序列,则在此描述的余下部分中保留各操作数不变。
4) 若 E2
与 E3
是同类型和同值类别的泛左值,则结果具有相同的类型和值类别,而若 E2
与 E3
至少有一个是位域,则结果为位域。
5) 否则,结果为纯右值。若 E2
与 E3
具有不同类型,且一者拥有(可为 cv 限定的)类类型,则尝试用下文的各内建候选函数将操作数转换为内建类型,并为此实施重载决议。若重载决议失败,则程序非良构。否则,应用所选择的转换,并在步骤 6 用转换后的操作数取代原操作数。
6) 对第二和第三操作数应用左值到右值、数组到指针和函数到指针转换,然后,
6.1) 若 E2
与 E3
现在均具有相同类型,则结果是该类型的纯右值,指代一个临时对象, (C++17 前)其结果对象 (C++17 起)从求值 E1
后选择的那个操作数进行复制初始化。
6.2) 若 E2
与 E3
均具有算术或枚举类型:则应用一般算术转换将它们变为公共类型,而该类型即是结果。
6.3) 若 E2
与 E3
均为指针,或一个是指针而另一个是空指针常量,则应用指针转换和限定性转换,将它们变为公共类型,而该类型即是结果。
6.4) 若 E2
与 E3
均为成员指针,或一个是成员指针而另一个是空指针常量,则应用成员指针转换和限定性转换,将它们变成公共类型,而该类型即是结果。
6.5) 若 E2
与 E3
均为空指针常量,而至少有一个为 std::nullptr_t 类型,则结果类型为 std::nullptr_t
。
6.6) 所有其他情况下,程序非良构。
本节未完成原因:任何令此更可读,而不遗漏重点的机会?退一步来说,给每条一个一行的小示例会大有裨益 |
对于每对提升后的算术类型 L 和 R 并对于每个类型 P,其中 P 为指针、成员指针或有作用域枚举类型,下列函数签名在上述规则的步骤 5 参与重载决议:
LR operator?:(bool, L, R ); | ||
P operator?:(bool, P, P ); | ||
其中 LR 是 L
和 R
上进行的一般算术转换的结果。不能重载运算符 “?:”,这些函数签名只为重载决议的目的存在。
条件运算符的返回类型亦能作为二元类型特性 std::common_type 访问。
运行此代码
- #include <string>
- #include <iostream>
- struct Node
- {
- Node* next;
- int data;
- // 深复制的复制构造函数
- Node(const Node& other)
- : next(other.next ? new Node(*other.next) : NULL)
- , data(other.data)
- {}
- Node(int d) : next(NULL), data(d) {}
- ~Node() { delete next ; }
- };
- int main()
- {
- // 简单的右值示例
- int n = 1 > 2 ? 10 : 11; // 1 > 2 为 false,故 n = 11
- // 简单的左值示例
- int m = 10;
- (n == m ? n : m) = 7; // n == m 为 false,故 m = 7
- std::cout << "n = " << n << "\nm = " << m; // 输出结果
- }
输出:
- n = 11
- m = 7
标准库
标准库中许多类都重载了 operator()
,以使其能被用作函数对象。
标准库中的任何类都不重载逗号运算符。boost 库将 operator,
用于 boost.assign、boost.spirit 和其他库。数据库访问库 SOCI 亦重载 operator,
。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1550 | C++98 | 当另一操作数并非 void 时 ?: 中不允许有括号的 throw 表达式 | 接受有括号的 throw 表达式 |
CWG 1560 | C++98 | ?: 中的 void 操作数导致另一操作数上无理由的左值到右值转换,始终产生右值 | 带 void 的 ?: 能为左值 |
CWG 1932 | C++14 | ?: 中缺失同类型位域 | 以底层类型处理 |
CWG 1895 | C++14 | 不明确弃置或不可访问的转换函数是否阻止 ?: 的转换,且未考虑从基类到派生类纯右值的转换 | 与重载决议相似的方式处理 |
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增自减 | 算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = ba += ba -= ba = ba /= ba %= ba &= ba |= ba ^= ba <<= ba >>= b | ++a—aa++a— | +a-aa + ba - ba ba / ba % b~aa & ba | ba ^ ba << ba >> b | !aa && ba || b | a == ba != ba < ba > ba <= ba >= ba <=> b | a[b]a&aa->ba.ba->ba.*b | a(…)a, b? : |
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型dynamic_cast 在继承层级中转换const_cast 添加或移除 cv 限定符reinterpret_cast 转换类型到无关类型C 风格转型 以 static_cast 、 const_cast 及 reinterpret_cast 的混合转换一个类型到另一类型new 创建有动态存储期的对象delete 销毁先前由 new 表达式创建的对象,并释放其所拥有的内存区域sizeof 查询类型的大小sizeof… 查询形参包的大小(C++11 起)typeid 查询类型的类型信息noexcept 查询表达式是否能抛出异常(C++11 起)alignof 查询类型的对齐要求(C++11 起) |