Other operators
|
运算符
名称 |
语法 | 可重载 | 原型示例(针对 class T ) | |
|---|---|---|---|---|
| 类内定义 | 类外定义 | |||
| 函数调用 |
a(a1, a2)
|
是 | R T :: operator ( ) ( Arg1 & a1, Arg2 & a2, ... ) ; | 不适用 |
| 逗号 |
a, b
|
是 | T2 & T :: operator , ( T2 & b ) ; | T2 & operator, ( const T & a, T2 & b ) ; |
| 条件运算符 |
a ? b : c
|
否 | 不适用 | 不适用 |
函数调用 操作符为任何对象提供函数语义。
条件运算符 (俗称 三元条件运算符 )会检查第一个表达式的布尔值,并根据结果值计算并返回第二个或第三个表达式。
目录 |
内建函数调用运算符
函数调用表达式具有以下形式:
函数
(
参数1
,
参数2
,
参数3
,
...
)
|
|||||||||
| function | - | 函数类型表达式或函数指针类型 |
arg1
,
arg2
,
arg3
,
...
|
- | 可能为空的任意表达式列表 或 花括号包围的初始化列表 (C++11 起) ,但顶层不允许使用逗号运算符以避免歧义 |
对于非成员函数或 静态成员函数 的调用, function 可以是引用函数的左值(此时会抑制 function-to-pointer conversion ),也可以是函数指针类型的纯右值。
由 function 指定的函数(或成员)名称可以被重载, 重载决议 规则用于决定调用哪个重载版本。
如果 function 指定的是成员函数,它可以是虚函数,在这种情况下将通过运行时动态分派调用该函数的最终覆写器。
每个函数参数都会在必要时经过 隐式转换 后,使用其对应的实参进行初始化。
- 若不存在对应实参,则使用对应的 默认实参 ,若不存在默认实参,则程序非良构。
- 若调用的是成员函数,则指向当前对象的 this 指针会如同显式转换般被转换为该函数所期望的 this 指针类型。
- 每个参数的初始化和析构发生于函数调用所在的 完整表达式 上下文中,这意味着例如:若参数的构造函数或析构函数抛出异常,则被调用函数的 函数 try 块 不会被纳入考虑范围。
如果函数是可变参数函数, 默认参数提升 将应用于与省略号参数匹配的所有参数。
形参是在其定义的函数退出时销毁,还是在封闭的完整表达式末尾销毁,这是由实现定义的。形参总是按照其构造的逆序进行销毁。
函数调用表达式的返回类型是所选函数的返回类型,该类型通过静态绑定确定(忽略 virtual 关键字),即使实际调用的重写函数返回不同类型。这允许重写函数返回派生自基函数返回类型的类的指针或引用,即C++支持 协变返回类型 。若 function 指定析构函数,则返回类型为 void 。
|
当类类型
临时对象分别由函数实参或返回值构造,函数的参数或返回对象通过使用非删除的平凡构造函数复制该临时对象进行初始化(即使该构造函数不可访问,或不会被重载决议选中来执行对象的复制或移动)。 这使得小型类类型对象(例如 std::complex 或 std::span )可以通过寄存器传递至函数或从函数返回。 |
(C++17 起) |
函数调用表达式的值类别为左值,若该函数返回左值引用或对函数的右值引用;为亡值,若函数返回对对象的右值引用;否则为纯右值。若函数调用表达式为对象类型的纯右值,则必须具有
完整类型
除非用作
decltype
的操作数(或作为
内建逗号运算符
的右操作数,且该逗号运算符是
decltype
的操作数)
(C++11 起)
。
|
当被调用函数正常退出时,该函数的所有
后置条件断言
将按
顺序依次求值
。若实现引入了任何
临时对象
来保存结果值,则对于每个后置条件断言求值
|
(since C++26) |
函数调用表达式在语法上类似于值初始化
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 起)
(C++23 前) |
|
未加括号的逗号表达式不能作为 下标运算符 的第二(右)参数。例如, a [ b, c ] 要么是病式代码,要么等价于 a. operator [ ] ( b, c ) 。 当使用逗号表达式作为下标时需要使用括号,例如 a [ ( b, c ) ] 。 |
(C++23 起) |
#include <iostream> int main() { // 逗号常用于在语言语法只允许单个表达式的位置执行多个表达式: // * 在 for 循环的第三个组件中 for (int i = 0, j = 10; i <= j; ++i, --j) // ^列表分隔符 ^逗号运算符 std::cout << "i = " << i << " j = " << j << '\n'; // * 在 return 语句中 // return log("an error!"), -1; // * 在初始化表达式中 // MyClass(const Arg& arg) // : member{ throws_if_bad(arg), arg } // 等等 // 逗号运算符可以链式使用;整个链的结果是最后一个(最右侧)表达式的结果: int n = 1; int m = (++n, std::cout << "n = " << n << '\n', ++n, 2 * n); // m 现在为 6 std::cout << "m = " << (++m, m) << '\n'; }
输出:
i = 0 j = 10 i = 1 j = 9 i = 2 j = 8 i = 3 j = 7 i = 4 j = 6 i = 5 j = 5 n = 2 m = 7
条件运算符
条件运算符表达式的形式为
E1
?
E2
:
E3
|
|||||||||
E1 被求值并 上下文转换 为 bool ,若结果为 true ,则条件表达式的结果为 E2 的值;否则条件表达式的结果为 E3 的值。
条件表达式 E1 ? E2 : E3 的类型和值类别按如下方式确定:
第一阶段
如果 E2 和 E3 都是 void 类型,则结果为 右值 (C++11 前) 纯右值 (C++11 起) ,其类型为 void 。
如果 E2 和 E3 中有且仅有一个属于 void 类型:
如果 E2 和 E3 都不是 void 类型,则进入下一阶段。
2 + 2 == 4 ? throw 123 : throw 456; // 结果类型为“void” 2 + 2 != 4 ? "OK" : throw "error"; // 结果类型为“const char[3]” // 即使始终抛出异常
第二阶段
如果
E2
或
E3
是
左值位域
(C++11 前)
相同值类别的泛左值位域
(C++11 起)
,且类型分别为
cv1
T
和
cv2
T
,则在后续处理中操作数被视为
cv
T
类型,其中
cv
是
cv1
与
cv2
的并集。
如果 E2 和 E3 具有不同的类型,且满足以下任一条件,则进入阶段3:
- 至少有一个 E2 和 E3 是(可能带有 cv 限定符的)类类型。
- E2 和 E3 两者都是 相同类型的左值 (C++11 前) 具有相同值类别和相同类型的泛左值 (C++11 起) (cv 限定符除外)。
否则,进入第4阶段。
阶段 3
尝试从类型为
TX
的操作数表达式
X
到与操作数表达式
Y
的类型
TY
相关的
目标类型
按如下方式形成
隐式转换序列
[2]
:
-
如果
Y
是左值,则目标类型为
TY&,但仅当该引用能够 直接绑定 到 左值 (C++11 前) 泛左值 (C++11 起) 时,才能形成隐式转换序列。
|
(C++11 起) |
-
如果
Y
是
右值
(C++11 前)
纯右值
(C++11 起)
,或者无法构成上述任何转换序列,且
TX与TY中至少有一个是(可能带有 cv 限定符的)类类型:-
如果
TX与TY是相同类类型(忽略 cv 限定符):-
如果
TY的 cv 限定符不少于TX,则目标类型为TY。 - 否则,不构成转换序列。
-
如果
-
否则,如果
TY是TX的基类,则目标类型为带有TXcv 限定符的TY。 - 否则,目标类型为 Z 的类型,其中 Z 是 Y 在应用左值到右值、数组到指针和函数到指针 标准转换 后的值。
-
如果
- 否则,不构成转换序列。
通过此过程,将确定是否可以从 E2 到为 E3 确定的目标类型形成隐式转换序列,反之亦然。
- 如果无法形成任何转换序列,则进入下一阶段。
-
如果恰好能形成一个转换序列:
- 若该转换序列存在歧义,则程序非良构。
- 否则,将选定的操作数应用此转换,并在后续处理中使用转换后的操作数替代原始操作数,随后进入下一阶段。
- 若两个序列均可形成,则程序非良构。
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
第四阶段
|
若 E2 与 E3 为同类型的左值,则结果为该类型的左值,且当至少一个操作数为位域时结果也为位域。 |
(C++11 前) |
|
若 E2 与 E3 为同类型且同值类别的泛左值,则结果具有相同类型和值类别,且当至少一个操作数为位域时结果也为位域。 |
(C++11 起) |
否则,结果为 右值 (C++11 前) 纯右值 (C++11 起) 。
- 如果 E2 与 E3 类型不同,且任一操作数具有(可能带有 cv 限定符的)类类型,则进入阶段 5。
- 否则,进入阶段 6。
第五阶段
重载决议 通过使用 内置候选函数 来尝试将操作数转换为内置类型:
- 若重载决议失败,则程序非良构。
- 否则,将应用选定的转换,转换后的操作数将替代原始操作数用于后续处理。进入下一阶段。
阶段 6
数组到指针和函数到指针的转换被应用于(可能经过转换的) E2 和 E3 。在这些转换之后,必须满足以下至少一个条件,否则程序将是非法的:
- E2 与 E3 具有相同类型。此时结果类型与该类型相同,且通过所选操作数进行 复制初始化 。
- E2 与 E3 均具有算术或枚举类型。此时应用 常用算术转换 以获取其公共类型,结果类型为该公共类型。
- E2 与 E3 至少有一个为指针类型。此时应用左值到右值转换、指针 、函数指针 (C++17 起) 及限定转换以获取其 复合指针类型 ,结果类型为该复合指针类型。
- E2 与 E3 至少有一个为成员指针类型。此时应用左值到右值转换、成员指针 、函数指针 (C++17 起) 及限定转换以获取其 复合指针类型 ,结果类型为该复合指针类型。
|
(C++11 起) |
int* intPtr; using Mixed = decltype(true ? nullptr : intPtr); static_assert(std::is_same_v<Mixed, int*>); // nullptr 转换为 int* 类型 struct A { int* m_ptr; } a; int* A::* memPtr = &A::m_ptr; // memPtr 是指向 A 类成员 m_ptr 的指针 // memPtr 使 nullptr 成为指向 A 类成员 m_ptr 的指针类型 static_assert(std::is_same_v<decltype(false ? memPtr : nullptr), int*A::*>); // a.*memPtr 现在只是指向 int 的指针,nullptr 也转换为指向 int 的指针 static_assert(std::is_same_v<decltype(false ? a.*memPtr : nullptr), int*>);
- ↑ 此类条件运算符在 C++14 之前常被用于 C++11 的 constexpr 编程 中。
- ↑ 成员访问 、转换函数是否被删除 (自 C++11 起) 以及操作数是否为位域均被忽略。
|
条件运算符的结果类型也可通过二元类型特征 std::common_type 访问。 |
(since C++11) |
重载
对于每一对提升的算术类型
L
和
R
,以及对于每个类型
P
(其中
P
是指针、成员指针或有作用域枚举类型),下列函数签名参与重载决议:
|
LR 运算符
?:
(
bool
, L, R
)
;
|
||
|
P 运算符
?:
(
bool
, P, P
)
;
|
||
其中 LR 是对
常规算术转换
应用于
L
和
R
后得到的结果。
运算符“
?:
”不可重载,这些函数签名的存在仅用于重载解析目的。
#include <iostream> #include <string> 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()
以用作函数对象。
|
删除对象或数组
(
std::default_delete<T>
的公开成员函数)
|
|
|
返回两个参数的和
(
std::plus<T>
的公开成员函数)
|
|
|
返回两个参数的差值
(
std::minus<T>
的公开成员函数)
|
|
|
返回两个参数的乘积
(
std::multiplies<T>
的公开成员函数)
|
|
|
返回第一个参数除以第二个参数的结果
(
std::divides<T>
的公开成员函数
)
|
|
|
返回第一个参数除以第二个参数的余数
(
std::modulus<T>
的公开成员函数
)
|
|
|
返回参数的逻辑非
(
std::negate<T>
的公开成员函数)
|
|
|
检查参数是否相等
(
std::equal_to<T>
的公开成员函数)
|
|
|
检查参数是否不相等
(
std::not_equal_to<T>
的公开成员函数)
|
|
|
检查第一个参数是否大于第二个参数
(
std::greater<T>
的公开成员函数)
|
|
|
检查第一个参数是否小于第二个参数
(
std::less<T>
的公开成员函数)
|
|
|
检查第一个参数是否大于或等于第二个参数
(
std::greater_equal<T>
的公开成员函数)
|
|
|
检查第一个参数是否小于或等于第二个参数
(
std::less_equal<T>
的公开成员函数)
|
|
|
返回两个参数的逻辑与运算结果
(
std::logical_and<T>
的公开成员函数)
|
|
|
返回两个参数的逻辑或运算结果
(
std::logical_or<T>
的公开成员函数)
|
|
|
返回参数的逻辑非运算结果
(
std::logical_not<T>
的公开成员函数)
|
|
|
返回两个参数的按位与运算结果
(
std::bit_and<T>
的公开成员函数)
|
|
|
返回两个参数的按位或运算结果
(
std::bit_or<T>
的公开成员函数)
|
|
|
返回两个参数的按位异或结果
(
std::bit_xor<T>
的公开成员函数)
|
|
|
返回对存储谓词调用结果的逻辑补
(
std::unary_negate<Predicate>
的公开成员函数)
|
|
|
返回存储谓词调用结果的逻辑补
(
std::binary_negate<Predicate>
的公开成员函数)
|
|
|
调用存储的函数
(
std::reference_wrapper<T>
的公开成员函数)
|
|
|
调用目标对象
(
std::function<R(Args...)>
的公开成员函数)
|
|
|
调用目标对象
(
std::move_only_function
的公开成员函数)
|
|
|
调用目标对象
(
std::copyable_function
的公开成员函数)
|
|
|
恢复协程的执行
(
std::coroutine_handle<Promise>
的公开成员函数)
|
|
|
使用此locale的collate facet按字典序比较两个字符串
(std::locale的公开成员函数) |
|
比较两个
value_type
类型的值
(
std::map<Key,T,Compare,Allocator>::value_compare
的公开成员函数)
|
|
比较两个
value_type
类型的值
(
std::multimap<Key,T,Compare,Allocator>::value_compare
的公开成员函数)
|
|
|
执行函数
(
std::packaged_task<R(Args...)>
的公开成员函数)
|
|
|
推进引擎状态并返回生成的值
(
std::linear_congruential_engine<UIntType,a,c,m>
的公开成员函数)
|
|
|
(C++11)
|
生成分布中的下一个随机数
(
std::uniform_int_distribution<IntType>
的公开成员函数)
|
标准库中没有任何类重载逗号运算符。boost库在 boost.assign 、 boost.spirit 及其他库中使用了 operator, 。数据库访问库 SOCI 也重载了 operator, 。
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
| DR | 适用版本 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 446 | C++98 | 未指明条件运算符中的左值到右值转换是否创建临时对象 | 若运算符返回类右值则始终创建临时对象 |
| CWG 462 | C++98 | 若逗号运算符的第二操作数为临时对象,当逗号表达式结果绑定到引用时是否延长其生存期未指明 | 此时逗号表达式结果即为该临时对象(因此其生存期被延长) |
| CWG 587 | C++98 | 当条件运算符的第二、三操作数为仅cv限定不同的同类型左值时,若操作数为类类型则结果为左值,否则为右值 | 此情况下结果始终为左值 |
| CWG 1029 | C++98 | 析构函数调用的类型未指明 | 明确为 void |
| CWG 1550 | C++98 | 带括号的 throw 表达式在另一操作数为非 void 类型时不允许用于条件表达式 | 允许使用 |
| CWG 1560 | C++98 | 条件运算符的 void 操作数导致对另一操作数进行不必要的左值到右值转换,始终产生右值结果 | 含 void 的条件表达式可为左值 |
| CWG 1642 | C++98 | 函数调用表达式中的 function 表达式可为函数指针左值 | 不允许 |
| CWG 1805 | C++98 | 确定隐式转换序列的目标类型时, Y 到 Z 的转换方式不明确 | 已明确 |
| CWG 1895 |
C++98
C++11 |
不明确被删除(C++11)或不可访问(C++98)的转换函数是否会阻止条件表达式中的转换,且未考虑从基类到派生类纯右值的转换 | 按重载决议方式处理 |
| CWG 1932 | C++98 | 条件表达式中缺失同类型位域的处理规则 | 通过底层类型处理 |
| CWG 2226 | C++11 | 确定条件运算符另一操作数的目标类型时,若该操作数为左值则引用不能绑定到亡值 | 允许绑定 |
| CWG 2283 | C++17 | 函数调用运算符的类型完整性要求被 P0135R1 意外移除 | 恢复该要求 |
| CWG 2321 | C++98 | 确定条件运算符另一操作数的目标类型时,派生类类型不能转换为cv限定更少的基类类型 | 允许转换为具有派生类操作数cv限定的基类类型 |
| CWG 2715 | C++98 | 每个参数的初始化与析构会在调用函数上下文中进行,但该上下文可能不存在 [1] | 在外层完整表达式上下文中进行 |
| CWG 2850 | C++98 | 参数析构顺序不明确 | 已明确 |
| CWG 2865 | C++98 |
若
TX
与
TY
为相同类类型且
TX
比
TY
具有更多cv限定,仍可从纯右值
Y
形成隐式转换序列
|
此情况下不会形成转换序列 |
| CWG 2906 | C++98 | 条件运算符在右值结果情况下无条件应用左值到右值转换 | 仅在部分情况下应用 |
- ↑ 例如,可以在命名空间作用域变量的初始化器中调用函数,但在此上下文中并不存在“调用函数”的概念。
另请参阅
| 常用运算符 | ||||||
|---|---|---|---|---|---|---|
| 赋值 |
递增
递减 |
算术 | 逻辑 | 比较 |
成员
访问 |
其他 |
|
a
=
b
|
++
a
|
+
a
|
!
a
|
a
==
b
|
a
[
...
]
|
函数调用
a ( ... ) |
|
逗号运算符
a, b |
||||||
|
条件运算符
a ? b : c |
||||||
| 特殊运算符 | ||||||
|
static_cast
将一种类型转换为另一种相关类型
|
||||||
|
C 文档
关于
其他运算符
|