其他运算符
运算符 名称 |
语法 | 可重载 | 原型示例(对于 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
|
否 | 不适用 | 不适用 |
“函数调用”运算符为任何对象提供函数语义。
“条件运算符”(俗称“三元条件运算符”)检查第一个表达式的布尔值,并根据结果值,评估并返回第二个或第三个表达式。
目录 |
[编辑] 内置函数调用运算符
函数调用表达式具有以下形式:
function ( arg1, arg2, arg3, ...) |
|||||||||
function | - | 函数类型或函数指针类型的表达式 |
arg1, arg2, arg3, ... |
- | 可能为空的任意表达式列表或花括号括起来的初始化器列表(C++11 起),但在顶层不允许使用逗号运算符以避免歧义 |
对于非成员函数或静态成员函数的调用,function可以是引用函数的左值(在这种情况下会抑制函数到指针的转换),也可以是函数指针类型的纯右值。
function指定的函数(或成员)名称可以重载,重载决议规则用于决定调用哪个重载。
如果function指定的是成员函数,它可以是虚函数,在这种情况下,将使用运行时动态派发调用该函数的最终覆盖器。
每个函数参数在必要时经过隐式转换后,用其对应的实参进行初始化。
- 如果没有对应的实参,则使用对应的默认实参;如果也没有,则程序格式错误。
- 如果调用的是成员函数,则当前对象的this指针将被转换,如同通过显式转换,以符合函数所期望的this指针类型。
- 每个参数的初始化和销毁发生在函数调用出现的完整表达式的上下文中,这意味着,例如,如果参数的构造函数或析构函数抛出异常,则不会考虑被调用函数的函数try块。
如果函数是变长参数函数,则对由省略号参数匹配的所有实参应用默认实参提升。
参数是在定义它的函数退出时销毁,还是在包含它的完整表达式结束时销毁,这是实现定义的。参数总是以其构造的逆序销毁。
函数调用表达式的返回类型是所选函数的返回类型,使用静态绑定决定(忽略virtual关键字),即使实际调用的覆盖函数返回不同的类型。这允许覆盖函数返回指向派生自基函数返回类型的类的指针或引用,即 C++ 支持协变返回类型。如果function指定的是析构函数,则返回类型为void。
当类类型`X`的对象作为函数参数传递或从函数返回时,如果`X`的每个复制构造函数、移动构造函数和析构函数都是平凡的或已删除的,并且`X`至少有一个未删除的复制或移动构造函数,则允许实现创建一个临时对象来保存函数参数或结果对象。 临时对象分别从函数实参或返回值构造,并且函数的参数或返回对象被初始化,如同使用未删除的平凡构造函数复制临时对象一样(即使该构造函数不可访问或不会被重载决议选择来执行对象的复制或移动)。 这允许将小型类类型的对象(例如std::complex或std::span)通过寄存器传递给函数或从函数返回。 |
(C++17 起) |
如果函数返回左值引用或函数右值引用,则函数调用表达式的值类别为左值;如果函数返回对象右值引用,则为亡值;否则为纯右值。如果函数调用表达式是对象类型的纯右值,则它必须具有完整类型,除非用作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到与操作数表达式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
的基类,则目标类型是具有TX
的cv限定符的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, 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> 的公共成员函数) | |
返回两个实参的逻辑 AND ( std::logical_and<T> 的公共成员函数) | |
返回两个实参的逻辑 OR ( 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> 的公共成员函数) | |
使用此区域设置的 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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 446 | C++98 | 对于条件运算符上的左值到右值转换,是否创建临时对象未指定 对于条件运算符上的左值到右值转换,是否创建临时对象未指定 |
如果运算符返回类右值 则总是创建临时对象 |
CWG 462 | C++98 | 如果逗号运算符的第二个操作数是临时对象, 当逗号表达式的结果绑定到引用时,其生命周期是否会延长未指定 当逗号表达式的结果绑定到引用时,其生命周期是否会延长未指定 |
在这种情况下,逗号表达式的结果 是临时对象 (因此其生命周期得以延长) |
CWG 587 | C++98 | 当条件运算符的第二和第三个操作数是具有相同类型(除了 cv 限定符)的左值时,如果这些操作数 具有类类型,则结果为左值,否则为右值 具有类类型,则结果为左值,否则为右值 |
在这种情况下,结果总是 左值 |
CWG 1029 | C++98 | 析构函数调用的类型未指定 | 指定为 void |
CWG 1550 | C++98 | 如果其他操作数是非 void 类型,则条件表达式中不允许带括号的 throw 表达式 如果其他操作数是非 void 类型,则条件表达式中不允许带括号的 throw 表达式 |
允许 |
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 | 在确定条件运算符的其他操作数 的目标类型时,如果该操作数是左值,则引用 无法绑定到 xvalue |
允许 |
CWG 2283 | C++17 | 函数调用运算符的类型完整性要求被 P0135R1 意外移除 函数调用运算符的类型完整性要求被 P0135R1 意外移除 |
恢复了要求 |
CWG 2321 | C++98 | 在确定条件运算符的其他操作数 的目标类型时,派生类类型无法 转换为 cv 限定符较少的基类类型 |
允许转换为基类类型, 使用来自派生类操作数的 cv 限定符 使用来自派生类操作数的 cv 限定符 |
CWG 2715 | C++98 | 每个参数的初始化和析构将发生 在调用函数的上下文中,这可能不存在[1] 在调用函数的上下文中,这可能不存在[1] |
发生在 封闭的完整表达式的上下文中 |
CWG 2850 | C++98 | 参数的析构顺序不明确 | 已明确 |
CWG 2865 | C++98 | 如果 TX 和 TY 是相同的类类型,并且 TX 的 cv 限定符比 TY 多,则仍然可以从纯右值 Y 形成隐式转换序列比 TY 多,则仍然可以从纯右值 Y 形成隐式转换序列 |
在这种情况下不会形成 转换序列 |
CWG 2906 | C++98 | 在条件运算符的右值结果情况下,无条件地应用左值到右值转换 在条件运算符的右值结果情况下,无条件地应用左值到右值转换 |
仅在某些情况下应用 |
- ↑ 例如,函数可以在命名空间范围变量的初始化器中调用,在这种情况下没有“调用函数”。
[编辑] 另请参阅
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 递增 递减 |
算术 | 逻辑 | 比较 | 成员 访问 |
其他 |
a = b |
++a |
+a |
!a |
a == b |
a[...] |
函数调用 a(...) |
逗号 a, b | ||||||
条件运算符 a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 将一种类型转换为另一种相关类型 |
C 文档 关于 其他运算符
|