其他运算符
运算符 名称 |
语法 | 可重载 | 原型示例(对于 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 |
函数调用运算符为任何对象提供函数语义。
条件运算符(俗称三元条件)检查第一个表达式的布尔值,并根据结果值,评估并返回第二个或第三个表达式。
目录 |
[编辑] 内建函数调用运算符
函数调用表达式具有以下形式
函数( 参数1, 参数2, 参数3, ...) |
|||||||||
函数 | - | 函数类型或函数指针类型的表达式 |
参数1, 参数2, 参数3, ... |
- | 可能为空的任意表达式列表 或 花括号初始化器列表(自 C++11 起),但顶层不允许使用逗号运算符以避免歧义 |
对于非成员函数或 静态成员函数 的调用,函数 可以是引用函数的左值(在这种情况下,函数到指针转换 会被抑制),或者函数指针类型的纯右值。
由 函数 指定的函数(或成员)名称可以重载,使用 重载决议 规则来决定要调用哪个重载。
如果 函数 指定成员函数,则它可以是虚函数,在这种情况下,将调用该函数的最终覆盖者,在运行时使用动态分发。
每个函数形参都使用其对应的实参进行初始化,如有必要,会进行 隐式转换。
- 如果没有对应的实参,则使用对应的 默认实参,如果没有默认实参,则程序是非良构的。
- 如果调用是针对成员函数的,则当前对象的 this 指针会被转换,如同显式转型为函数期望的 this 指针。
- 每个形参的初始化和销毁都发生在函数调用出现的 完整表达式 的上下文中,这意味着,例如,如果形参的构造函数或析构函数抛出异常,则不考虑被调用函数的 函数 try 块。
如果函数是可变参数函数,则 默认实参提升 将应用于省略号形参匹配的所有实参。
形参是在定义它的函数退出时销毁,还是在封闭的完整表达式结束时销毁,这是实现定义的。形参始终按照构造的相反顺序销毁。
函数调用表达式的返回类型是所选函数的返回类型,使用静态绑定(忽略 virtual 关键字)决定,即使实际调用的覆盖函数返回不同的类型也是如此。这允许覆盖函数返回指向从基函数返回的返回类型派生的类的指针或引用,即 C++ 支持 协变返回类型。如果 函数 指定析构函数,则返回类型为 void。
当类类型 临时对象分别从函数实参或返回值构造,并且函数形参或返回对象被初始化,如同使用未删除的平凡构造函数复制临时对象(即使该构造函数不可访问或不会通过重载决议选择来执行对象的复制或移动)。 这允许小型类类型的对象,例如 std::complex 或 std::span,在寄存器中传递给函数或从函数返回。 |
(自 C++17 起) |
函数调用表达式的值类别是:如果函数返回左值引用或函数类型的右值引用,则为左值;如果函数返回对象类型的右值引用,则为 xvalue;否则为纯右值。如果函数调用表达式是对象类型的纯右值,则它必须具有 完整类型,除非用作 decltype
的操作数(或作为 内建逗号运算符 的右操作数,而该逗号运算符又是 decltype 的操作数)(自 C++11 起)。
当被调用的函数正常退出时,该函数的所有 后置条件断言 都将按 顺序求值。如果实现引入任何 临时对象 来保存结果值,则对于每个后置条件断言的求值 |
(自 C++26 起) |
函数调用表达式在语法上类似于值初始化 T(),函数式转型 表达式 T(A1),以及临时的直接初始化 T(A1, A2, A3, ...),其中 T
是类型名称。
#include <cstdio> struct S { int f1(double d) { return printf("%f \n", d); // variable argument function call } int f2() { return f1(7); // member function call, same as this->f1() // integer argument converted to double } }; void f() { puts("function called"); // function call } int main() { f(); // function call S s; s.f2(); // member function call }
输出
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() { // comma is often used to execute more than one expression // where the language grammar allows only one expression: // * in the third component of the for loop for (int i = 0, j = 10; i <= j; ++i, --j) // ^list separator ^comma operator std::cout << "i = " << i << " j = " << j << '\n'; // * in a return statement // return log("an error!"), -1; // * in an initializer expression // MyClass(const Arg& arg) // : member{ throws_if_bad(arg), arg } // etc. // comma operators can be chained; the result of the last // (rightmost) expression is the result of the whole chain: int n = 1; int m = (++n, std::cout << "n = " << n << '\n', ++n, 2 * n); // m is now 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 的类型和值类别按如下方式确定:
[编辑] 阶段 1
如果 E2 和 E3 的类型均为 void,则结果是 一个右值(在 C++11 之前)一个纯右值(自 C++11 起),类型为 void。
如果 E2 和 E3 中恰好有一个类型为 void
如果 E2 和 E3 的类型都不是 void,则继续到下一阶段。
2 + 2 == 4 ? throw 123 : throw 456; // the result is of type “void” 2 + 2 != 4 ? "OK" : throw "error"; // the result is of type “const char[3]” // even if an exception is always thrown
[编辑] 阶段 2
如果 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 到与类型为 TY
的操作数表达式 Y 的目标类型相关的 隐式转换序列[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
的基类,则目标类型为TY
,并带有TX
的 cv 限定符。 - 否则,目标类型是 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, Target = const A
[编辑] 阶段 4
如果 E2 和 E3 是相同类型的左值,则结果是该类型的左值,并且如果 E2 和 E3 中至少有一个是位域,则结果是位域。 |
(在 C++11 之前) |
如果 E2 和 E3 是相同类型和相同值类别的泛左值,则结果具有相同的类型和值类别,并且如果 E2 和 E3 中至少有一个是位域,则结果是位域。 |
(自 C++11 起) |
否则,结果是 一个右值(在 C++11 之前)一个纯右值(自 C++11 起)。
- 如果 E2 和 E3 的类型不同,并且其中任何一个具有(可能带 cv 限定的)类类型,则继续到阶段 5。
- 否则,继续到阶段 6。
[编辑] 阶段 5
使用 内建候选函数 执行 重载决议,以尝试将操作数转换为内建类型。
- 如果重载决议失败,则程序是非良构的。
- 否则,应用选定的转换,并将转换后的操作数代替原始操作数用于剩余过程。继续到下一阶段。
[编辑] 阶段 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 becoming int* struct A { int* m_ptr; } a; int* A::* memPtr = &A::m_ptr; // memPtr is a pointer to member m_ptr of A // memPtr makes nullptr as type of pointer to member m_ptr of A static_assert(std::is_same_v<decltype(false ? memPtr : nullptr), int*A::*>); // a.*memPtr is now just pointer to int and nullptr also becomes pointer to int static_assert(std::is_same_v<decltype(false ? a.*memPtr : nullptr), int*>);
- ↑ 这种条件运算符在 C++14 之前的 C++11 constexpr 编程 中很常用。
- ↑ 成员访问、转换函数是否已删除(自 C++11 起) 以及操作数是否为位域均被忽略。
条件运算符的结果类型也可以通过二元类型特征 std::common_type 访问。 |
(自 C++11 起) |
[编辑] 重载
对于每对提升后的算术类型 L
和 R
以及每种类型 P
(其中 P
是指针、指向成员的指针或作用域枚举类型),以下函数签名参与重载决议
LR operator?:(bool, L, R); |
||
P operator?:(bool, P, P); |
||
其中 LR 是对 L
和 R
执行常用算术转换的结果。
运算符“?:
”不能被重载,这些函数签名仅用于重载决议的目的。
#include <iostream> #include <string> struct Node { Node* next; int data; // deep-copying copy constructor 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() { // simple rvalue example int n = 1 > 2 ? 10 : 11; // 1 > 2 is false, so n = 11 // simple lvalue example int m = 10; (n == m ? n : m) = 7; // n == m is false, so m = 7 //output the result 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> 的公共成员函数) | |
使用此区域设置的排序规则方面按字典顺序比较两个字符串 ( 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 限定,如果这些操作数具有类类型,则结果为左值,否则为右值 cv 限定,如果这些操作数具有类类型,则结果为左值,否则为右值 操作数具有类类型或右值 |
结果始终是 在这种情况下为左值 |
CWG 1029 | C++98 | 析构函数调用的类型未指定 | 指定为 void |
CWG 1550 | C++98 | 如果另一个操作数是非 void 类型,则条件表达式中不允许使用带括号的 throw 表达式 条件表达式中不允许使用带括号的 throw 表达式 如果另一个操作数是非 void 类型 |
接受 |
CWG 1560 | C++98 | 条件运算符的 void 操作数导致 在另一个操作数上进行多余的左值到右值转换,总是导致右值 在另一个操作数上进行多余的左值到右值转换,总是导致右值 |
带有 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 | 当确定条件运算符的另一个操作数的目标类型时,如果该操作数是左值,则引用可能无法绑定到 xvalue 当确定条件运算符的另一个操作数的目标类型时,如果该操作数是左值,则引用可能无法绑定到 xvalue 值,则引用可能无法绑定到 xvalue |
允许 |
CWG 2283 | C++17 | 函数调用运算符的类型完整性要求被 P0135R1 意外删除 函数调用运算符的类型完整性要求被 P0135R1 意外删除 |
恢复了要求 |
CWG 2321 | C++98 | 当确定条件运算符的另一个操作数的目标类型时,派生类类型可能无法转换为较少 cv 限定的基类类型 当确定条件运算符的另一个操作数的目标类型时,派生类类型可能无法转换为较少 cv 限定的基类类型 类类型可能无法转换为较少 cv 限定的基类类型 |
允许转换为具有来自派生类操作数的 cv 限定的基类类型 类类型与 cv 限定 来自派生类操作数 |
CWG 2715 | C++98 | 每个参数的初始化和销毁将在调用函数的上下文中发生,这可能不存在[1] 参数的初始化和销毁将在调用函数的上下文中发生,这可能不存在[1] 函数的上下文中发生,这可能不存在[1] |
在封闭的完整表达式的上下文中发生 在封闭的完整表达式的上下文中发生 |
CWG 2850 | C++98 | 参数的销毁顺序不明确 | 已明确 |
CWG 2865 | C++98 | 如果 TX 和 TY 是相同的类类型,并且 TX 比 TY 具有更多的 cv 限定符,则隐式转换序列仍然可以从纯右值 Y 形成如果 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 文档 关于 其他运算符
|