运算符重载

运算符重载

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 m;,m[1] = 2; 调用 m.operator[](1)

a->

(a).operator->( )

不能为非成员

给定 std::unique_ptr p;,p->bar() 调用 p.operator->()

a@

(a).operator@ (0)

operator@ (a, 0)

给定 std::vector::iterator i;,i++ 调用 i.operator++(0)

在此表格中,@ 是一个占位符,表示所有匹配的运算符:@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 v = {1, 2, 3, 4, 5};

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 m{};

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;

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 上的运算符重载

相关推荐

杭州十大家具市场榜中榜 365手机版游戏中心官网

杭州十大家具市场榜中榜

📅 01-20 👁️ 5749
电脑内置7.1声卡怎么调试 创新7.1声卡调试步骤 365bet取款要多久到账

电脑内置7.1声卡怎么调试 创新7.1声卡调试步骤

📅 08-29 👁️ 6398
西班牙对塞尔维亚 交手统计数据 365scores下载

西班牙对塞尔维亚 交手统计数据

📅 09-01 👁️ 2284
水牢副本通关关键:六人协同+技能配置实战! 365scores下载

水牢副本通关关键:六人协同+技能配置实战!

📅 08-14 👁️ 4182