Namespaces
Variants

Other operators

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous
运算符
名称
语法 可重载 原型示例(针对 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 指定的函数(或成员)名称可以被重载, 重载解析 规则用于决定调用哪个重载版本。

如果 function 指定的是成员函数,它可以是虚函数,此时将通过运行时动态分派调用该函数的最终覆盖版本。

每个函数参数都会在必要时经过 隐式转换 后,使用其对应的实参进行初始化。

  • 若没有对应的实参,则使用对应的 默认实参 ;若不存在默认实参,则程序非良构。
  • 若调用的是成员函数,则指向当前对象的 this 指针会如同显式转换般转换为该函数所期望的 this 指针类型。
  • 每个参数的初始化和析构都在函数调用出现的 完整表达式 上下文中进行,这意味着:例如,若参数的构造函数或析构函数抛出异常,被调用函数的 函数 try 不会被纳入考虑范围。

若函数为可变参数函数, 默认参数提升 将应用于与省略号参数匹配的所有实参。

参数的销毁时机是由实现定义的,可能在定义该参数的函数退出时销毁,也可能在外层完整表达式结束时销毁。参数的销毁顺序始终与其构造顺序相反。

函数调用表达式的返回类型是所选函数的返回类型,该类型通过静态绑定确定(忽略 virtual 关键字),即使实际调用的重写函数返回不同类型也是如此。这允许重写函数返回派生自基函数返回类型的类的指针或引用,即 C++ 支持 协变返回类型 。若 function 指定了析构函数,则返回类型为 void

当类类型 X 的对象被传入函数或从函数返回时,若 X 的每个拷贝构造函数、移动构造函数和析构函数均为平凡(trivial)或被删除,且 X 至少存在一个未被删除的拷贝或移动构造函数,则实现允许创建临时对象来保存函数参数或结果对象。

该临时对象分别由函数实参或返回值构造,而函数的形参或返回对象通过使用未被删除的平凡构造函数复制临时对象进行初始化(即使该构造函数不可访问,或不会被重载决议选中来执行对象的拷贝或移动)。

这使得小型类类型对象(如 std::complex std::span )可以通过寄存器传递至函数或从函数返回。

(C++17 起)

函数调用表达式的值类别如下:若函数返回左值引用或函数的右值引用,则为左值;若函数返回对象的右值引用,则为亡值;其余情况均为纯右值。若函数调用表达式为对象类型的纯右值,则必须具有 完整类型 ,除非用作 decltype 的操作数(或作为 内置逗号运算符 的右操作数,且该逗号运算符是 decltype 的操作数) (C++11 起)

当被调用函数正常退出时,该函数的所有 后置条件断言 将按 顺序依次求值 。若实现引入了任何 临时对象 来保存结果值,则对于每个后置条件断言求值 E

(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 的类型与值类别按以下规则确定:

阶段 1

如果 E2 E3 都是 void 类型,则结果为 右值 (C++11 前) 纯右值 (C++11 起) ,其类型为 void

如果 E2 E3 中恰好有一个是 void 类型:

  • 如果该类型为 void 的操作数是一个(可能带括号的) throw 表达式 ,则结果具有另一操作数的类型和值类别 [1] 。如果另一操作数是 位域 ,则结果也是位域。
  • 否则,程序非良构。

如果 E2 E3 均不属于 void 类型,则进入下一阶段。

2 + 2 == 4 ? throw 123 : throw 456; // 结果类型为“void”
2 + 2 != 4 ? "OK" : throw "error";  // 结果类型为“const char[3]”
                                    // 即使始终会抛出异常

阶段 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 起) 时才能形成隐式转换序列。
  • 如果 Y 是 xvalue,则目标类型为 TY&& ,但仅当引用可直接绑定时才能形成隐式转换序列。
(since 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, 目标类型 = const A

第四阶段

如果 E2 E3 是相同类型的左值,则结果是该类型的左值,并且当 E2 E3 中至少有一个是位域时,结果也是位域。

(C++11 前)

如果 E2 E3 是相同类型和相同值类别的泛左值,则结果具有相同的类型和值类别,并且当 E2 E3 中至少有一个是位域时,结果也是位域。

(C++11 起)

否则,结果是 an rvalue (until C++11) a prvalue (since C++11)

  • 如果 E2 E3 类型不同,且任一操作数具有(可能带有 cv 限定符的)类类型,则进入阶段 5。
  • 否则,进入阶段 6。

阶段 5

重载解析 通过使用 内置候选函数 来执行,尝试将操作数转换为内置类型:

  • 如果重载决议失败,则程序非良构。
  • 否则,将应用选定的转换,转换后的操作数将替代原始操作数用于后续处理。进入下一阶段。

阶段6

数组到指针和函数到指针的转换被应用于(可能经过转换的) E2 E3 。在这些转换之后,必须满足以下至少一个条件,否则程序将是非法的:

  • E2 E3 具有相同类型。此时结果类型与该类型相同,并通过 复制初始化 使用所选操作数生成结果。
  • E2 E3 均具有算术或枚举类型。此时应用 常用算术转换 将其提升至公共类型,结果类型为该公共类型。
  • E2 E3 中至少有一个为指针。此时应用左值到右值转换、指针 、函数指针 (C++17 起) 及限定转换,将其提升至 复合指针类型 ,结果类型为该复合指针类型。
  • E2 E3 中至少有一个为成员指针。此时应用左值到右值转换、成员指针 、函数指针 (C++17 起) 及限定转换,将其提升至 复合指针类型 ,结果类型为该复合指针类型。
(since 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*>);
  1. 此类条件运算符在C++14之前常被用于C++11的 常量表达式编程 中。
  2. 成员访问 、转换函数是否被删除 (自C++11起) 以及操作数是否为位域均被忽略。

条件运算符的结果类型也可通过二元类型特征 std::common_type 访问。

(since 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;
    // 深拷贝复制构造函数
    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> 的公开成员函数)

标准库中没有任何类重载逗号运算符。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 表达式 允许使用
CWG 1560 C++98 条件运算符的 void 操作数会导致对其他操作数进行不必要的左值到右值转换,始终产生右值结果 void 操作数的条件表达式可为左值
CWG 1642 C++98 函数调用表达式中的 函数 表达式可以是函数指针左值 不允许
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 条件运算符在右值结果情况下无条件应用左值到右值转换 仅在部分情况下应用
  1. 例如,函数可以在命名空间作用域变量的初始化器中调用,在此上下文中不存在“调用函数”的概念。

参见

运算符优先级
运算符重载

常用运算符
赋值 自增
自减
算术 逻辑 比较 成员
访问
其他

a = b
a + = b
a - = b
a * = b
a / = b
a % = b
a & = b
a | = b
a ^ = b
a <<= b
a >>= b

++ a
-- a
a ++
a --

+ a
- a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

! a
a && b
a || b

a == b
a ! = b
a < b
a > b
a <= b
a >= b
a <=> b

a [ ... ]
* a
& a
a - > b
a. b
a - > * b
a. * b

函数调用

a ( ... )
逗号

a, b
条件

a ? b : c
特殊运算符

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 起)

C 文档 关于 其他运算符