其他运算符
运算符 名称 |
语法 | 可重载 | 原型示例(对于 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 |
函数调用 运算符为任何对象提供函数语义。
条件运算符(俗称三元条件 )检查第一个表达式的布尔值,并根据结果值,计算并返回第二个或第三个表达式。
内容 |
[编辑] 内置函数调用运算符
函数调用表达式具有以下形式
function ( arg1, arg2, arg3, ...) |
|||||||||
function | - | 函数类型或函数指针类型的表达式 |
arg1, arg2, arg3, ... |
- | 可能是空的任意表达式列表 或 大括号括起来的初始化列表(自 C++11 起),除了逗号运算符不允许在顶层使用以避免歧义 |
对于对非成员函数或对 静态成员函数 的调用,function 可以是引用函数的左值(在这种情况下,函数到指针的转换 会被抑制),或者可以是函数指针类型的右值。
由 function 指定的函数(或成员)名称可以重载,重载解析 规则用于决定要调用哪个重载。
如果 function 指定了一个成员函数,它可能是虚函数,在这种情况下,将使用运行时的动态调度调用该函数的最终覆盖者。
要调用函数,
表达式 function 以及作为参数提供的所有表达式 arg1、arg2、arg3 等,以任意顺序进行评估,无序 彼此之间。 |
(直到 C++17) |
function 位于 arg1、arg2、arg3 以及 默认参数(如果有)之前。参数表达式按任意顺序进行评估,不确定排序 彼此之间。 |
(从 C++17 开始) |
每个函数参数在 隐式转换(如果有)之后用其相应的参数初始化。
- 如果没有相应的参数,则使用相应的 默认参数,如果没有,则程序格式错误。
- 如果对成员函数进行调用,则当前对象的 this 指针将被转换为函数期望的 this 指针,就像使用显式强制转换一样。
- 每个参数的初始化和销毁发生在函数调用出现的 完整表达式 的上下文中,这意味着,例如,如果参数的构造函数或析构函数抛出异常,则调用的函数的 函数 try 块 不会被考虑。
如果函数是可变参数函数,则 默认参数提升 将应用于由省略号参数匹配的所有参数。
定义它的函数退出时或包含的完整表达式结束时是否销毁参数是实现定义的。参数始终以与构造相反的顺序销毁。
函数调用表达式的返回类型是所选函数的返回类型,使用静态绑定确定(忽略 virtual 关键字),即使实际调用的覆盖函数返回不同的类型。这允许覆盖函数返回指向从基函数返回的类型派生的类的指针或引用,即 C++ 支持 协变返回类型。如果 function 指定了一个析构函数,则返回类型为 void。
当将类类型 临时对象分别从函数参数或返回值构造,函数的参数或返回值对象被初始化,就像使用未删除的平凡构造函数复制临时对象一样(即使该构造函数是不可访问的或不会被重载解析选择来执行对象的复制或移动)。 这允许小类类型的对象(例如 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); // 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
[edit] 内置逗号运算符
逗号表达式具有以下形式
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
[edit] 条件运算符
条件运算符表达式具有以下形式
E1 ? E2 : E3 |
|||||||||
条件运算符的第一个操作数被评估并 上下文转换为 bool。在第一个操作数的值评估和所有副作用都完成后,如果结果为 true,则评估第二个操作数。如果结果为 false,则评估第三个操作数。
条件表达式 E1 ? E2 : E3 的类型和值类别根据以下规则确定
- 如果 E2 或 E3 的类型为 void
2 + 2 == 4 ? throw 123 : throw 456; std::string str = 2 + 2 == 4 ? "OK" : throw std::logic_error("2 + 2 != 4");
- 否则,如果 E2 或 E3 是 左值位域(直到 C++11)具有相同值类别的通用左值位域(从 C++11 开始) 并且类型分别为 cv1
T
和 cv2T
,则在本文档的其余部分中,操作数被认为是类型为 cvT
的,其中 cv 是 cv1 和 cv2 的并集。
- 否则,如果 E2 和 E3 具有不同的类型,其中至少有一个是(可能带 cv 限定符的)类类型,或者两者都是 左值(直到 C++11)具有相同值类别的通用左值(从 C++11 开始) 并且除了 cv 限定符之外具有相同的类型,则尝试形成 隐式转换序列。[2]
- 尝试从类型为
TX
的操作数表达式 X 到与操作数表达式 Y 的类型TY
相关的 目标类型 形成隐式转换序列,如下所示- 如果 Y 是左值,目标类型为
TY&
,但只有在引用能够 直接绑定 到 左值(直到 C++11)左值(自 C++11 起) 时,才能形成隐式转换序列。
- 如果 Y 是左值,目标类型为
|
(自 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 的值。
- 如果
- 否则,不会形成任何转换序列。
- 如果 Y 是 右值(直到 C++11)纯右值(自 C++11 起),或者上述转换序列中没有一个可以形成,并且
- 使用此过程,可以确定是否可以从 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
- 如果 E2 和 E3 是相同类型和相同值类别的左值,则结果具有相同的类型和值类别,并且如果 E2 和 E3 中至少有一个是位字段,则结果是位字段。
- 否则,结果是纯右值。
-
- 如果重载解析失败,则程序格式错误。
- 否则,将应用所选转换,并将转换后的操作数用于剩余过程中的原始操作数。
- 将左值到右值、数组到指针和函数到指针转换应用于(可能已转换的) E2 和 E3。在这些转换之后,必须满足以下条件之一,否则程序格式错误
- E2 和 E3 具有相同的类型。在这种情况下,结果是该类型的纯右值 指定一个临时对象(直到 C++17)其结果对象是(自 C++17 起) 从评估 E1 后所选的操作数进行复制初始化。
- E2 和 E3 都是算术类型或枚举类型。在这种情况下,将应用 通常的算术转换 将它们转换为它们的公共类型,结果为该类型。
- E2 和 E3 都是指针,或者一个是指针,另一个是 空指针常量,则将应用指针转换和限定符转换将它们转换为它们的 组合指针类型,结果为该类型。
- 如果 E2 和 E3 都是指向成员的指针,或者一个是指向成员的指针,另一个是空指针常量,则将应用指向成员的指针转换和限定符转换将它们转换为组合指针类型,结果为该类型。
|
(自 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++11 中用于 constexpr 编程,早于 C++14。
- ↑ 成员访问,无论是转换函数是否被删除(自 C++11 起) 以及操作数是否是位字段,都会被忽略。
本节不完整 原因:是否有机会使它更易读,而不会丢失要点?至少,每个要点的一行微型示例将大有帮助 |
[edit] 重载
对于每对提升的算术类型 L
和 R
,以及对于每种类型 P
,其中 P
是指针、指向成员的指针或作用域枚举类型,以下函数签名参与重载解析
LR operator?:(bool, L, R); |
||
P operator?:(bool, P, P); |
||
其中 LR 是对 L
和 R
执行 通常的算术转换 所得的结果。
运算符“?:
”不能重载,这些函数签名只存在于重载解析的目的。
条件运算符的结果类型也可以作为二进制类型特质 std::common_type 访问。 |
(自 C++11 起) |
#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
[edit] 标准库
标准库中的许多类都重载了 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...)> 的公共成员函数) | |
(C++11) |
推进引擎的状态并返回生成的值。 ( std::linear_congruential_engine<UIntType,a,c,m> 的公共成员函数) |
(C++11) |
生成分布中的下一个随机数。 ( std::uniform_int_distribution<IntType> 的公共成员函数) |
逗号运算符不会被标准库中的任何类重载。Boost 库在 boost.assign、boost.spirit 和其他库中使用 operator,。数据库访问库 SOCI 也重载了 operator,。
[edit] 缺陷报告
以下行为更改的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布的行为 | 正确行为 |
---|---|---|---|
CWG 446 | C++98 | 未指定是否为条件运算符上的左值到右值转换创建临时对象 如果运算符返回类右值,则始终创建临时对象 |
始终创建临时对象 运算符返回类右值 |
CWG 462 | C++98 | 如果逗号运算符的第二个操作数是临时对象,则未指定其生命周期在逗号表达式的结果绑定到引用时是否会延长 逗号表达式的结果 在这种情况下是临时对象 |
逗号表达式的结果 在这种情况下是临时对象 (因此其生命周期会延长) |
CWG 587 | C++98 | 当条件运算符的第二个和第三个操作数是除了 cv 限定之外类型相同的左值时,如果这些操作数具有类类型,则结果为左值;否则为右值 结果始终 在这种情况下是左值 结果始终 |
在这种情况下是左值 结果始终 |
在这种情况下是左值 | C++98 | 结果始终 | 在这种情况下是左值 |
CWG 1029 | C++98 | 析构函数调用的类型未指定 指定为 void |
指定为 void |
指定为 void | C++98 | 指定为 void CWG 1550 如果另一个操作数是非 void,则不允许在条件表达式中使用带括号的 throw 表达式 |
接受 接受 |
接受 | C++98 | 接受 CWG 1560 |
条件运算符的 void 操作数会导致对另一个操作数进行不必要的左值到右值转换,始终导致右值 |
具有 void 的条件表达式可以是左值 | C++98 | 具有 void 的条件表达式可以是左值 具有 void 的条件表达式可以是左值 |
具有 void 的条件表达式可以是左值 |
CWG 1642 | C++98 函数调用表达式中的表达式 function 可以是函数指针左值 |
不允许 不允许 不允许 不允许 |
CWG 1805 在确定隐式转换序列的目标类型时,将 Y 转换为 Z 的方式尚不清楚 |
已明确 | C++98 | 已明确 | 已明确 |
已明确 | 函数调用表达式中的表达式 function 可以是函数指针左值 | CWG 1895 C++11 不清楚是已删除 (C++11) 还是不可访问 (C++98) |
转换函数阻止条件表达式中的转换,并且没有考虑从基类到派生类右值的转换 |
像重载解析一样处理 | C++98 | 像重载解析一样处理 像重载解析一样处理 像重载解析一样处理 |
CWG 1932 条件表达式中缺少相同类型的位字段 由基础类型处理 |
由基础类型处理 | C++98 | 由基础类型处理 由基础类型处理 CWG 2226 |
在确定条件运算符的另一个操作数的目标类型时,如果该操作数是左值,则引用无法绑定到右值 允许 |
允许 | C++98 | 允许 | 具有 void 的条件表达式可以是左值 |
允许 | C++98 | CWG 2321 在确定条件运算符的另一个操作数的目标类型时,派生类类型无法转换为 cv 限定程度更低的基类类型 允许转换为具有 cv 限定程度的基类类型 |
允许转换为具有 cv 限定程度的基类类型 允许转换为具有 cv 限定程度的基类类型 |
- 允许转换为具有 cv 限定程度的基类类型
来自派生类操作数的 cv 限定程度
来自派生类操作数的 cv 限定程度 | ||||||
---|---|---|---|---|---|---|
来自派生类操作数的 cv 限定程度 | 来自派生类操作数的 cv 限定程度 CWG 2715 |
每个参数的初始化和销毁将在调用函数的上下文中发生,而调用函数可能不存在[1] | 在封闭的完整表达式的上下文中发生 | 在封闭的完整表达式的上下文中发生 | 在封闭的完整表达式的上下文中发生 在封闭的完整表达式的上下文中发生 |
CWG 2850 |
参数的销毁顺序尚不清楚 |
递增 |
比较 |
a <<= b |
--a |
a * b |
函数调用 |
a(...) | ||||||
逗号 | ||||||
a, b | ||||||
条件运算符 | ||||||
a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 将一种类型转换为另一种相关类型 |
C 文档 用于 其他运算符
|