C++
编译器支持
自由(freestanding)与宿主(hosted)
语言
标准库
标准库头文件
具名要求
特性测试宏 (C++20)
语言支持库
概念库 (C++20)
诊断库
内存管理库
元编程库 (C++11)
通用工具库
容器库
迭代器库
范围库 (C++20)
算法库
字符串库
文本处理库
数值库
日期和时间库
输入/输出库
文件系统库 (C++17)
并发支持库 (C++11)
执行控制库 (C++26)
技术规范
符号索引
外部库
[编辑] C++ 语言
通用主题
预处理器
注释
关键词
转义序列
流程控制
条件执行语句
if
switch
迭代语句(循环)
for
范围-for (C++11)
while
do-while
跳转语句
continue - break
goto - return
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
throw 表达式
try 块
catch 处理程序
命名空间
命名空间声明
命名空间别名
类型
基本类型
枚举类型
函数类型
类/结构体类型
联合类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
默认初始化
值初始化
零初始化
复制初始化
直接初始化
聚合初始化
列表初始化 (C++11)
常量初始化
引用初始化
表达式
值类别
求值顺序
运算符
运算符优先级
替代表示
字面量
布尔 - 整型 - 浮点型
字符 - 字符串 - nullptr (C++11)
用户定义 (C++11)
工具
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
类型转换
隐式转换
static_cast
const_cast
显式转换
dynamic_cast
reinterpret_cast
内存分配
new 表达式
delete 表达式
类
类声明
构造函数
this 指针
访问说明符
friend 说明符
类特有的函数属性
虚函数
override 说明符 (C++11)
final 说明符 (C++11)
explicit (C++11)
static
特殊成员函数
默认构造函数
复制构造函数
移动构造函数 (C++11)
复制赋值
移动赋值 (C++11)
析构函数
模板
类模板
函数模板
模板特化
参数包 (C++11)
杂项
内联汇编
C++ 历史
[编辑] 表达式
通用
值类别
求值顺序
常量表达式
主表达式
Lambda 表达式 (C++11)
Requires 表达式 (C++20)
包索引表达式 (C++26)
潜在求值表达式
字面量
整数字面量
浮点数字面量
布尔字面量
字符字面量
转义序列
字符串字面量
空指针字面量 (C++11)
用户定义字面量 (C++11)
运算符
赋值运算符
递增和递减
算术运算符
逻辑运算符
比较运算符
成员访问运算符
其他运算符
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof... (C++11)
typeid
noexcept (C++11)
折叠表达式 (C++17)
运算符的替代表示
优先级和结合性
运算符重载
默认比较 (C++20)
转换
隐式转换
显式转换
常用算术转换
用户定义转换
const_cast
static_cast
dynamic_cast
reinterpret_cast
[编辑]
为用户定义类型的操作数定制 C++ 运算符。
目录
1 语法
2 解释
2.1 静态重载运算符
3 限制
4 规范实现
4.1 赋值运算符
4.2 流提取和插入
4.3 函数调用运算符
4.4 自增和自减
4.5 二元算术运算符
4.6 比较运算符
4.7 数组下标运算符
4.8 位算术运算符
4.9 布尔非运算符
4.10 不常重载的运算符
5 注释
6 关键词
7 示例
8 缺陷报告
9 参阅
10 外部链接
[编辑] 语法
运算符函数 是具有特殊函数名称的函数
operator op
(1)
operator newoperator new []
(2)
operator deleteoperator delete []
(3)
operator co_await
(4)
(C++20 起)
op
-
以下任一运算符:+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=>(C++20起) && || ++ -- , ->* -> () []
1) 重载的标点符号运算符。
2) 分配函数。
3) 解除分配函数。
4) 重载的 co_await 运算符,用于 co_await 表达式。
非标点运算符的行为在其各自页面中描述。除非另有说明,本页面中的其余描述不适用于这些函数。
[编辑] 解释
当运算符出现在表达式中,并且其操作数至少有一个是类类型或枚举类型时,则使用重载决议来确定要调用的用户定义函数,该函数是从所有签名匹配以下条件的函数中选择的
表达式
作为成员函数
作为非成员函数
示例
@a
(a).operator@ ( )
operator@ (a)
!std::cin 调用 std::cin.operator!()
a@b
(a).operator@ (b)
operator@ (a, b)
std::cout << 42 调用 std::cout.operator<<(42)
a=b
(a).operator= (b)
不能为非成员
给定 std::string s;,s = "abc"; 调用 s.operator=("abc")
a(b...)
(a).operator()(b...)
不能为非成员
给定 std::random_device r;,auto n = r(); 调用 r.operator()()
a[b...]
(a).operator[](b...)
不能为非成员
给定 std::map
a->
(a).operator->( )
不能为非成员
给定 std::unique_ptr p;,p->bar() 调用 p.operator->()
a@
(a).operator@ (0)
operator@ (a, 0)
给定 std::vector
在此表格中,@ 是一个占位符,表示所有匹配的运算符:@a 中的所有前缀运算符,a@ 中除 -> 之外的所有后缀运算符,a@b 中除 = 之外的所有中缀运算符。
此外,对于比较运算符 ==、!=、<、>、<=、>=、<=>,重载决议还会考虑重写的候选 operator== 或 operator<=>。
(C++20 起)
重载运算符(而非内置运算符)可以使用函数表示法调用
std::string str = "Hello, ";
str.operator+=("world"); // same as str += "world";
operator<<(operator<<(std::cout, str), '\n'); // same as std::cout << str << '\n';
// (since C++17) except for sequencing
静态重载运算符
作为成员函数的重载运算符可以声明为静态。然而,这只允许用于 operator() 和 operator[]。
此类运算符可以使用函数表示法调用。然而,当这些运算符出现在表达式中时,它们仍然需要一个类类型的对象。
struct SwapThem
{
template
static void operator()(T& lhs, T& rhs)
{
std::ranges::swap(lhs, rhs);
}
template
static void operator[](T& lhs, T& rhs)
{
std::ranges::swap(lhs, rhs);
}
};
inline constexpr SwapThem swap_them{};
void foo()
{
int a = 1, b = 2;
swap_them(a, b); // OK
swap_them[a, b]; // OK
SwapThem{}(a, b); // OK
SwapThem{}[a, b]; // OK
SwapThem::operator()(a, b); // OK
SwapThem::operator[](a, b); // OK
SwapThem(a, b); // error, invalid construction
SwapThem[a, b]; // error
}
(C++23 起)
[编辑] 限制
运算符函数必须至少有一个函数参数或隐式对象参数,其类型为类、对类的引用、枚举或对枚举的引用。运算符 ::(作用域解析)、.(成员访问)、.*(通过成员指针访问成员)和 ?:(三元条件)不能被重载。不能创建新的运算符,例如 **、<> 或 &|。不可能改变运算符的优先级、分组或操作数数量。运算符 -> 的重载必须返回原始指针,或返回一个对象(通过引用或值),该对象的 -> 运算符又被重载。运算符 && 和 || 的重载会失去短路求值特性。
当 &&、|| 和 , 被重载时,它们会失去特殊的序列化特性,即使不使用函数调用表示法,它们也表现得像常规函数调用。
(C++17 前)
[编辑] 规范实现
除了上述限制之外,语言对重载运算符的行为或返回类型(它不参与重载决议)没有其他限制,但通常,重载运算符预期尽可能与内置运算符的行为相似:operator+ 预期是加法,而不是乘法;operator= 预期是赋值等等。相关运算符预期行为相似(operator+ 和 operator+= 执行相同的加法类操作)。返回类型受限于运算符预期使用的表达式:例如,赋值运算符通过引用返回,以使 a = b = c = d 成为可能,因为内置运算符允许这样做。
常用的重载运算符具有以下典型的规范形式:[1]
[编辑] 赋值运算符
赋值运算符 operator= 具有特殊属性:详细信息请参见复制赋值和移动赋值。
规范的复制赋值运算符预期对自赋值是安全的,并通过引用返回左值
// copy assignment
T& operator=(const T& other)
{
// Guard self assignment
if (this == &other)
return *this;
// assume *this manages a reusable resource, such as a heap-allocated buffer mArray
if (size != other.size) // resource in *this cannot be reused
{
temp = new int[other.size]; // allocate resource, if throws, do nothing
delete[] mArray; // release resource in *this
mArray = temp;
size = other.size;
}
std::copy(other.mArray, other.mArray + other.size, mArray);
return *this;
}
规范的移动赋值预期将移动源对象置于有效状态(即,类不变量保持不变的状态),并且在自赋值时不做任何事情或至少将对象置于有效状态,并通过对非 const 的引用返回左值,并且为 noexcept。
// move assignment
T& operator=(T&& other) noexcept
{
// Guard self assignment
if (this == &other)
return *this; // delete[]/size=0 would also be ok
delete[] mArray; // release resource in *this
mArray = std::exchange(other.mArray, nullptr); // leave other in valid state
size = std::exchange(other.size, 0);
return *this;
}
(C++11 起)
在复制赋值不能从资源重用中受益(它不管理堆分配的数组,并且没有(可能是传递的)成员这样做,例如 std::vector 或 std::string 成员)的情况下,有一种流行的便捷简写:复制并交换赋值运算符,它通过值接收其参数(因此根据参数的值类别,既可作为复制赋值又可作为移动赋值),与参数交换,然后让析构函数清理它。
// copy assignment (copy-and-swap idiom)
T& T::operator=(T other) noexcept // call copy or move constructor to construct other
{
std::swap(size, other.size); // exchange resources between *this and other
std::swap(mArray, other.mArray);
return *this;
} // destructor of other is called to release the resources formerly managed by *this
此形式自动提供强异常保证,但禁止资源重用。
[编辑] 流提取和插入
接受 std::istream& 或 std::ostream& 作为左操作数的 operator>> 和 operator<< 的重载称为插入和提取运算符。由于它们将用户定义类型作为右操作数(a @ b 中的 b),因此它们必须作为非成员函数实现。
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if (/* T could not be constructed */)
is.setstate(std::ios::failbit);
return is;
}
这些运算符有时作为友元函数实现。
[编辑] 函数调用运算符
当用户定义的类重载函数调用运算符 operator() 时,它成为一个函数对象类型。
这种类型的对象可以在函数调用表达式中使用
// An object of this type represents a linear function of one variable a * x + b.
struct Linear
{
double a, b;
double operator()(double x) const
{
return a * x + b;
}
};
int main()
{
Linear f{2, 1}; // Represents function 2x + 1.
Linear g{-1, 0}; // Represents function -x.
// f and g are objects that can be used like a function.
double f_0 = f(0);
double f_1 = f(1);
double g_0 = g(0);
}
许多标准库算法接受函数对象以自定义行为。 operator() 没有特别值得注意的规范形式,但为了说明用法
运行此代码
#include
#include
#include
struct Sum
{
int sum = 0;
void operator()(int n) { sum += n; }
};
int main()
{
std::vector
Sum s = std::for_each(v.begin(), v.end(), Sum());
std::cout << "The sum is " << s.sum << '\n';
}
输出
The sum is 15
[编辑] 自增和自减
当后缀自增或自减运算符出现在表达式中时,相应的用户定义函数(operator++ 或 operator--)将以整数参数 0 调用。通常,它被声明为 T operator++(int) 或 T operator--(int),其中参数被忽略。后缀自增和自减运算符通常通过前缀版本实现
struct X
{
// prefix increment
X& operator++()
{
// actual increment takes place here
return *this; // return new value by reference
}
// postfix increment
X operator++(int)
{
X old = *this; // copy old value
operator++(); // prefix increment
return old; // return old value
}
// prefix decrement
X& operator--()
{
// actual decrement takes place here
return *this; // return new value by reference
}
// postfix decrement
X operator--(int)
{
X old = *this; // copy old value
operator--(); // prefix decrement
return old; // return old value
}
};
尽管前缀自增和自减运算符的规范实现通过引用返回,但与任何运算符重载一样,返回类型是用户定义的;例如,std::atomic 的这些运算符的重载通过值返回。
[编辑] 二元算术运算符
二元运算符通常实现为非成员函数以保持对称性(例如,当添加一个复数和一个整数时,如果 operator+ 是复数类型的成员函数,那么只有 complex + integer 可以编译,而 integer + complex 则不能)。因为每个二元算术运算符都存在一个相应的复合赋值运算符,二元运算符的规范形式是通过其复合赋值来实现的
class X
{
public:
X& operator+=(const X& rhs) // compound assignment (does not need to be a member,
{ // but often is, to modify the private members)
/* addition of rhs to *this takes place here */
return *this; // return the result by reference
}
// friends defined inside class body are inline and are hidden from non-ADL lookup
friend X operator+(X lhs, // passing lhs by value helps optimize chained a+b+c
const X& rhs) // otherwise, both parameters may be const references
{
lhs += rhs; // reuse compound assignment
return lhs; // return the result by value (uses move constructor)
}
};
[编辑] 比较运算符
标准库算法,例如 std::sort,以及容器,例如 std::set,默认要求为用户提供的类型定义 operator<,并要求它实现严格弱序(从而满足比较要求)。为结构实现严格弱序的惯用方法是使用 std::tie 提供的字典序比较
struct Record
{
std::string name;
unsigned int floor;
double weight;
friend bool operator<(const Record& l, const Record& r)
{
return std::tie(l.name, l.floor, l.weight)
< std::tie(r.name, r.floor, r.weight); // keep the same order
}
};
通常,一旦提供了 operator<,其他关系运算符都会通过 operator< 来实现。
inline bool operator< (const X& lhs, const X& rhs) { /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs) { return rhs < lhs; }
inline bool operator<=(const X& lhs, const X& rhs) { return !(lhs > rhs); }
inline bool operator>=(const X& lhs, const X& rhs) { return !(lhs < rhs); }
同样,不相等运算符通常通过 operator== 实现。
inline bool operator==(const X& lhs, const X& rhs) { /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs) { return !(lhs == rhs); }
当提供了三路比较(例如 std::memcmp 或 std::string::compare)时,所有六个双向比较运算符都可以通过它来表达
inline bool operator==(const X& lhs, const X& rhs) { return cmp(lhs,rhs) == 0; }
inline bool operator!=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) != 0; }
inline bool operator< (const X& lhs, const X& rhs) { return cmp(lhs,rhs) < 0; }
inline bool operator> (const X& lhs, const X& rhs) { return cmp(lhs,rhs) > 0; }
inline bool operator<=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) <= 0; }
inline bool operator>=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) >= 0; }
[编辑] 数组下标运算符
提供数组式访问并允许读写的用户定义类通常为 operator[] 定义两个重载:const 和非 const 变体
struct T
{
value_t& operator[](std::size_t idx) { return mVector[idx]; }
const value_t& operator[](std::size_t idx) const { return mVector[idx]; }
};
或者,它们可以表示为使用显式对象参数的单个成员函数模板
struct T
{
decltype(auto) operator[](this auto& self, std::size_t idx)
{
return self.mVector[idx];
}
};
(C++23 起)
如果已知值类型为标量类型,则 const 变体应按值返回。
如果不需要或不可能直接访问容器的元素,或者需要区分左值 c[i] = v; 和右值 v = c[i]; 的用法,operator[] 可以返回一个代理。例如参见 std::bitset::operator[]。
operator[] 只能带一个下标。为了提供多维数组访问语义,例如实现 3D 数组访问 a[i][j][k] = x;,operator[] 必须返回一个 2D 平面的引用,该平面必须有自己的 operator[],它返回一个 1D 行的引用,该行必须有 operator[],它返回元素的引用。为了避免这种复杂性,一些库选择重载 operator(),以便 3D 访问表达式具有类似 Fortran 的语法 a(i, j, k) = x;。
(直至 C++23)
operator[] 可以接受任意数量的下标。例如,一个 3D 数组类的 operator[],声明为 T& operator[](std::size_t x, std::size_t y, std::size_t z);,可以直接访问元素。
运行此代码
#include
#include
#include
template
struct Array3d
{
std::array
constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
{
assert(x < X and y < Y and z < Z);
return m[z * Y * X + y * X + x];
}
};
int main()
{
Array3d
v[3, 2, 1] = 42;
std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}
输出
v[3, 2, 1] = 42
(C++23 起)
[编辑] 位算术运算符
实现位掩码类型要求的用户定义类和枚举需要重载位算术运算符 operator&、operator|、operator^、operator~、operator&=、operator|= 和 operator^=,并且可以选择重载移位运算符 operator<< operator>>、operator>>= 和 operator<<=。规范实现通常遵循上述二元算术运算符的模式。
[编辑] 布尔非运算符
运算符 operator! 通常由旨在用于布尔上下文的用户定义类重载。此类类还提供一个用户定义的转换为布尔类型的函数(参见 std::basic_ios 的标准库示例),operator! 的预期行为是返回与 operator bool 相反的值。
(C++11 前)
由于内置运算符 ! 执行到 bool 的上下文转换,旨在用于布尔上下文的用户定义类可以只提供 operator bool 而无需重载 operator!。
(C++11 起)
[编辑] 不常重载的运算符
以下运算符很少重载
取址运算符,operator&。如果一元 & 应用于不完整类型的左值,并且完整类型声明了重载的 operator&,则未指定该运算符是具有内置含义还是调用运算符函数。因为此运算符可以重载,通用库使用 std::addressof 获取用户定义类型对象的地址。规范重载 operator& 最著名的例子是 Microsoft 类 CComPtrBase。该运算符在 EDSL 中的使用示例可以在 boost.spirit 中找到。布尔逻辑运算符 operator&& 和 operator||。与内置版本不同,重载无法实现短路求值。也与内置版本不同,它们不会在右操作数之前对左操作数进行排序。(C++17 前) 在标准库中,这些运算符仅为 std::valarray 重载。逗号运算符,operator,。与内置版本不同,重载不会在右操作数之前对其左操作数进行排序。(C++17 前) 由于此运算符可以重载,通用库使用诸如 a, void(), b 而非 a, b 来对用户定义类型的表达式执行排序。Boost 库在 boost.assign、boost.spirit 和其他库中使用了 operator,。数据库访问库 SOCI 也重载了 operator,。通过成员指针进行成员访问 operator->*。重载此运算符没有特定的缺点,但在实践中很少使用。有人建议它可以作为智能指针接口的一部分,实际上,boost.phoenix 中的 actor 就以这种方式使用它。在 EDSLs(例如 cpp.react)中更常见。
[编辑] 注意
特性测试宏
值
标准
特性
__cpp_static_call_operator
202207L
(C++23)
static operator()
__cpp_multidimensional_subscript
202211L
(C++23)
static operator[]
[编辑] 关键词
operator
[编辑] 示例
运行此代码
#include
class Fraction
{
// or C++17's std::gcd
constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
int n, d;
public:
constexpr Fraction(int n, int d = 1) : n(n / gcd(n, d)), d(d / gcd(n, d)) {}
constexpr int num() const { return n; }
constexpr int den() const { return d; }
constexpr Fraction& operator*=(const Fraction& rhs)
{
int new_n = n * rhs.n / gcd(n * rhs.n, d * rhs.d);
d = d * rhs.d / gcd(n * rhs.n, d * rhs.d);
n = new_n;
return *this;
}
};
std::ostream& operator<<(std::ostream& out, const Fraction& f)
{
return out << f.num() << '/' << f.den();
}
constexpr bool operator==(const Fraction& lhs, const Fraction& rhs)
{
return lhs.num() == rhs.num() && lhs.den() == rhs.den();
}
constexpr bool operator!=(const Fraction& lhs, const Fraction& rhs)
{
return !(lhs == rhs);
}
constexpr Fraction operator*(Fraction lhs, const Fraction& rhs)
{
return lhs *= rhs;
}
int main()
{
constexpr Fraction f1{3, 8}, f2{1, 2}, f3{10, 2};
std::cout << f1 << " * " << f2 << " = " << f1 * f2 << '\n'
<< f2 << " * " << f3 << " = " << f2 * f3 << '\n'
<< 2 << " * " << f1 << " = " << 2 * f1 << '\n';
static_assert(f3 == f2 * 10);
}
输出
3/8 * 1/2 = 3/16
1/2 * 5/1 = 5/2
2 * 3/8 = 3/4
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告
应用于
发布时的行为
正确的行为
CWG 1481
C++98
非成员前缀自增运算符只能有一个参数的类类型、枚举类型或对此类类型的引用类型
无类型要求
CWG 2931
C++23
显式对象成员运算符函数只能没有参数的类类型、枚举类型或对此类类型的引用类型
已禁止
[编辑] 另请参见
运算符优先级
替代运算符语法
实参依赖查找
常见运算符
赋值
递增递减
算术
逻辑
比较
成员访问
其他
a = ba += ba -= ba *= ba /= ba %= ba &= ba |= ba ^= ba <<= ba >>= b
++a
--aa++a--
+a
-aa + ba - ba * ba / ba % b~aa & ba | ba ^ ba << ba >> b
!aa && ba || b
a == ba != ba < ba > ba <= ba >= ba <=> b
a[...]
*a
&aa->ba.ba->*ba.*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 起)
[编辑] 外部链接
↑ StackOverflow C++ FAQ 上的运算符重载