运算符重载
运算符重载 下面按 “一类一类运算符” 给你讲。你可以把运算符重载理解成: 让自己写的类,也能像 int、double 一样使用 + / == () << 这些符号。 0. 运算符重载基本格式 成员函数写法 例如: 调用: 本质: 全局函数写法 例如: 调用: 本质: 1. 算术运算符重载 包括:...
运算符重载
下面按**“一类一类运算符”**给你讲。你可以把运算符重载理解成:
让自己写的类,也能像
int、double一样使用+ - * / == [] () <<这些符号。
0. 运算符重载基本格式
成员函数写法
返回类型 operator运算符(参数列表) {
// ...
}
例如:
Vector operator+(const Vector& other) const;
调用:
a + b
本质:
a.operator+(b)
全局函数写法
返回类型 operator运算符(参数1, 参数2) {
// ...
}
例如:
Vector operator+(const Vector& a, const Vector& b);
调用:
a + b
本质:
operator+(a, b)
1. 算术运算符重载
包括:
+ - * / %
常用于向量、复数、矩阵、时间类。
1.1 重载 +
比如三维向量相加:
class Vector3 {
private:
int x, y, z;
public:
Vector3(int x = 0, int y = 0, int z = 0)
: x(x), y(y), z(z) {}
Vector3 operator+(const Vector3& other) const {
return Vector3(x + other.x, y + other.y, z + other.z);
}
};
使用:
Vector3 a(1, 2, 3);
Vector3 b(4, 5, 6);
Vector3 c = a + b;
注意:
Vector3 operator+(const Vector3& other) const
这里最后的 const 表示:
这个
+不会修改当前对象。
也就是说:
c = a + b;
正常情况下,a 和 b 都不应该变。
1.2 重载 -
Vector3 operator-(const Vector3& other) const {
return Vector3(x - other.x, y - other.y, z - other.z);
}
使用:
Vector3 c = a - b;
1.3 重载 *
比如向量乘一个整数:
Vector3 operator*(int k) const {
return Vector3(x * k, y * k, z * k);
}
使用:
Vector3 b = a * 3;
这个只能支持:
a * 3
不支持:
3 * a
因为 3 * a 的左边是 int,不会去调用 a.operator*(3)。
如果你想支持 3 * a,一般写成全局函数:
friend Vector3 operator*(int k, const Vector3& v);
Vector3 operator*(int k, const Vector3& v) {
return Vector3(v.x * k, v.y * k, v.z * k);
}
1.4 重载 /
比如向量除以整数:
Vector3 operator/(int k) const {
return Vector3(x / k, y / k, z / k);
}
可以加判断:
Vector3 operator/(int k) const {
if (k == 0) {
throw runtime_error("division by zero");
}
return Vector3(x / k, y / k, z / k);
}
1.5 重载 %
这个比较少见,但也可以。
比如一个数类:
class Number {
private:
int value;
public:
Number(int value = 0) : value(value) {}
Number operator%(const Number& other) const {
return Number(value % other.value);
}
};
2. 复合赋值运算符重载
包括:
+= -= *= /= %=
这类运算符一般会修改当前对象。
2.1 重载 +=
Vector3& operator+=(const Vector3& other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}
使用:
a += b;
本质:
a.operator+=(b);
为什么返回 Vector3&?
因为可以支持链式操作:
a += b += c;
也符合 C++ 内置类型的习惯。
2.2 重载 -=
Vector3& operator-=(const Vector3& other) {
x -= other.x;
y -= other.y;
z -= other.z;
return *this;
}
2.3 重载 *=
Vector3& operator*=(int k) {
x *= k;
y *= k;
z *= k;
return *this;
}
2.4 重载 /=
Vector3& operator/=(int k) {
x /= k;
y /= k;
z /= k;
return *this;
}
2.5 推荐写法:用 += 实现 +
很多时候可以这样写:
Vector3& operator+=(const Vector3& other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}
Vector3 operator+(const Vector3& other) const {
Vector3 temp = *this;
temp += other;
return temp;
}
这样逻辑不重复。
3. 比较运算符重载
包括:
== != < > <= >=
返回值一般是 bool。
3.1 重载 ==
bool operator==(const Vector3& other) const {
return x == other.x && y == other.y && z == other.z;
}
使用:
if (a == b) {
cout << "same";
}
3.2 重载 !=
可以直接利用 ==:
bool operator!=(const Vector3& other) const {
return !(*this == other);
}
3.3 重载 <
对于时间类很常见:
class ClockTime {
private:
int hour;
int minute;
public:
ClockTime(int h = 0, int m = 0)
: hour(h), minute(m) {}
bool operator<(const ClockTime& other) const {
return hour * 60 + minute < other.hour * 60 + other.minute;
}
};
使用:
ClockTime a(10, 30);
ClockTime b(12, 0);
if (a < b) {
cout << "a is earlier";
}
3.4 重载 >
bool operator>(const ClockTime& other) const {
return other < *this;
}
意思是:
a > b
等价于:
b < a
3.5 重载 <=
bool operator<=(const ClockTime& other) const {
return !(*this > other);
}
3.6 重载 >=
bool operator>=(const ClockTime& other) const {
return !(*this < other);
}
3.7 比较运算符常见套路
一般只需要认真写:
operator<
operator==
其他的可以用它们推出来:
bool operator!=(const T& other) const {
return !(*this == other);
}
bool operator>(const T& other) const {
return other < *this;
}
bool operator<=(const T& other) const {
return !(*this > other);
}
bool operator>=(const T& other) const {
return !(*this < other);
}
4. 赋值运算符重载 =
这个非常重要,尤其是类里面有指针、动态数组的时候。
4.1 默认赋值的问题
比如:
class IntArray {
private:
int* data;
int size;
public:
IntArray(int n) {
size = n;
data = new int[size];
}
~IntArray() {
delete[] data;
}
};
如果你这样:
IntArray a(5);
IntArray b(10);
b = a;
默认赋值会把 a.data 的地址直接复制给 b.data。
结果就是:
a.data 和 b.data 指向同一块内存
析构时会重复释放,容易炸。
4.2 正确重载 operator=
class IntArray {
private:
int* data;
int size;
public:
IntArray(int n = 0) {
size = n;
data = new int[size];
}
~IntArray() {
delete[] data;
}
IntArray& operator=(const IntArray& other) {
if (this == &other) {
return *this;
}
delete[] data;
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
return *this;
}
};
重点:
if (this == &other)
是防止自己给自己赋值:
a = a;
4.3 operator= 必须是成员函数
赋值运算符不能写成普通全局函数,必须写在类里面。
T& operator=(const T& other);
5. 自增自减运算符重载
包括:
++ --
分为前置和后置。
5.1 前置 ++
++a;
写法:
T& operator++() {
// 修改自己
return *this;
}
例子:
class Counter {
private:
int x;
public:
Counter(int x = 0) : x(x) {}
Counter& operator++() {
x++;
return *this;
}
int value() const {
return x;
}
};
使用:
Counter a(5);
++a; // a 变成 6
前置 ++ 的特点:
先加,再返回加完后的自己。
5.2 后置 ++
a++;
写法:
T operator++(int) {
T old = *this;
// 修改自己
return old;
}
注意这个 int 是假的,只是为了区分前置和后置。
完整例子:
class Counter {
private:
int x;
public:
Counter(int x = 0) : x(x) {}
Counter& operator++() {
x++;
return *this;
}
Counter operator++(int) {
Counter old = *this;
x++;
return old;
}
int value() const {
return x;
}
};
使用:
Counter a(5);
Counter b = ++a; // a = 6, b = 6
Counter c = a++; // c = 6, a = 7
5.3 前置 --
T& operator--() {
// 修改自己
return *this;
}
例如:
Counter& operator--() {
x--;
return *this;
}
5.4 后置 --
T operator--(int) {
T old = *this;
x--;
return old;
}
6. 一元运算符重载
包括:
+ - ! ~
注意这里的 +、- 是一元运算符,不是加法减法。
6.1 一元负号 -
比如向量取反:
class Vector3 {
private:
int x, y, z;
public:
Vector3(int x = 0, int y = 0, int z = 0)
: x(x), y(y), z(z) {}
Vector3 operator-() const {
return Vector3(-x, -y, -z);
}
};
使用:
Vector3 a(1, 2, 3);
Vector3 b = -a; // (-1, -2, -3)
6.2 一元正号 +
这个比较少用。
Vector3 operator+() const {
return *this;
}
使用:
Vector3 b = +a;
一般没什么实际意义。
6.3 逻辑非 !
比如判断一个对象是否为空:
class MyString {
private:
string s;
public:
MyString(string s = "") : s(s) {}
bool operator!() const {
return s.empty();
}
};
使用:
MyString str("");
if (!str) {
cout << "empty";
}
6.4 按位取反 ~
比如集合类里面表示补集,或者位集合:
class Bits {
private:
int value;
public:
Bits(int value = 0) : value(value) {}
Bits operator~() const {
return Bits(~value);
}
};
7. 输入输出运算符重载
包括:
<< >>
这两个非常常见。
7.1 输出运算符 <<
一般写成全局函数,而且通常配合 friend。
#include <iostream>
using namespace std;
class Vector3 {
private:
int x, y, z;
public:
Vector3(int x = 0, int y = 0, int z = 0)
: x(x), y(y), z(z) {}
friend ostream& operator<<(ostream& out, const Vector3& v);
};
ostream& operator<<(ostream& out, const Vector3& v) {
out << "(" << v.x << ", " << v.y << ", " << v.z << ")";
return out;
}
使用:
Vector3 a(1, 2, 3);
cout << a << endl;
为什么返回 ostream&?
为了支持连续输出:
cout << a << b << endl;
如果你返回 void,就不能连续输出。
7.2 输入运算符 >>
class Vector3 {
private:
int x, y, z;
public:
Vector3(int x = 0, int y = 0, int z = 0)
: x(x), y(y), z(z) {}
friend istream& operator>>(istream& in, Vector3& v);
friend ostream& operator<<(ostream& out, const Vector3& v);
};
istream& operator>>(istream& in, Vector3& v) {
in >> v.x >> v.y >> v.z;
return in;
}
ostream& operator<<(ostream& out, const Vector3& v) {
out << "(" << v.x << ", " << v.y << ", " << v.z << ")";
return out;
}
使用:
Vector3 a;
cin >> a;
cout << a;
注意:
istream& operator>>(istream& in, Vector3& v)
这里的 v 不能加 const,因为输入要修改对象。
8. 下标运算符重载 []
[] 必须写成成员函数。
8.1 普通版本
class IntArray {
private:
int data[100];
public:
int& operator[](int index) {
return data[index];
}
};
使用:
IntArray arr;
arr[0] = 10;
为什么返回 int&?
因为:
arr[0] = 10;
左边要能被修改。
如果返回 int,那只是返回副本,不能真正修改数组元素。
8.2 const 版本
const int& operator[](int index) const {
return data[index];
}
完整写法:
class IntArray {
private:
int data[100];
public:
int& operator[](int index) {
return data[index];
}
const int& operator[](int index) const {
return data[index];
}
};
为什么要两个版本?
普通对象:
IntArray a;
a[0] = 10;
调用:
int& operator[](int index)
const 对象:
const IntArray a;
cout << a[0];
调用:
const int& operator[](int index) const
9. 函数调用运算符重载 ()
这个重载后,对象就可以像函数一样调用。
这种对象叫:
函数对象 / 仿函数
9.1 基本例子
class GreaterThan {
private:
int baseline;
public:
GreaterThan(int x) : baseline(x) {}
bool operator()(const int& value) const {
return value > baseline;
}
};
使用:
GreaterThan gt(10);
cout << gt(15); // true
cout << gt(5); // false
本质:
gt.operator()(15);
9.2 在 find_if 中使用
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class GreaterThan {
private:
int baseline;
public:
GreaterThan(int x) : baseline(x) {}
bool operator()(const int& value) const {
return value > baseline;
}
};
int main() {
vector<int> a{5, 10, 15, 20};
auto it = find_if(a.begin(), a.end(), GreaterThan(10));
if (it != a.end()) {
cout << *it; // 15
}
}
10. 成员访问运算符重载 ->
operator-> 比较特殊,常用于智能指针、迭代器。
10.1 基本例子
class Person {
public:
string name;
void sayHello() {
cout << "Hello, I am " << name << endl;
}
};
class PersonPtr {
private:
Person* ptr;
public:
PersonPtr(Person* p) : ptr(p) {}
Person* operator->() {
return ptr;
}
};
使用:
Person p;
p.name = "Tom";
PersonPtr pp(&p);
pp->sayHello();
本质上:
pp.operator->()->sayHello();
10.2 operator-> 的返回值要求
operator-> 的返回值必须是:
- 裸指针;
- 或者是另一个也重载了
operator->的对象。
最常见是返回裸指针:
T* operator->();
11. 解引用运算符重载 *
这个也常用于智能指针、迭代器。
class PersonPtr {
private:
Person* ptr;
public:
PersonPtr(Person* p) : ptr(p) {}
Person& operator*() {
return *ptr;
}
Person* operator->() {
return ptr;
}
};
使用:
Person p;
PersonPtr pp(&p);
(*pp).name = "Tom";
pp->sayHello();
12. 类型转换运算符重载
这个比较容易被忽略。
它可以让对象自动转换成某种类型。
12.1 转换成 int
class Integer {
private:
int value;
public:
Integer(int value = 0) : value(value) {}
operator int() const {
return value;
}
};
使用:
Integer a(10);
int x = a; // 自动转成 int
cout << a + 5; // a 转成 int 后参与计算
12.2 转换成 bool
class MyString {
private:
string s;
public:
MyString(string s = "") : s(s) {}
operator bool() const {
return !s.empty();
}
};
使用:
MyString str("hello");
if (str) {
cout << "not empty";
}
12.3 更推荐 explicit operator bool
为了避免乱转换,一般推荐:
explicit operator bool() const {
return !s.empty();
}
这样允许:
if (str) {
cout << "not empty";
}
但不太容易发生奇怪的隐式转换。
13. 逻辑运算符重载
包括:
&& || !
其中 ! 前面讲过。
&& 和 || 虽然可以重载,但一般不推荐。
13.1 重载 &&
class Flag {
private:
bool value;
public:
Flag(bool value = false) : value(value) {}
bool operator&&(const Flag& other) const {
return value && other.value;
}
};
使用:
Flag a(true), b(false);
if (a && b) {
cout << "true";
}
13.2 为什么不推荐重载 && 和 ||
因为内置的 && 和 || 有短路特性。
例如:
if (p != nullptr && p->value > 0)
如果 p == nullptr,后面的 p->value 不会执行。
但是你重载的 operator&& 不一定有这种短路效果,容易造成误解。
所以实际写类的时候,比较少重载 && 和 ||。
14. 位运算符重载
包括:
& | ^ ~ << >>
注意:
<< 和 >> 既可以是位移,也可以是输入输出。
14.1 重载 &
class Bits {
private:
int value;
public:
Bits(int value = 0) : value(value) {}
Bits operator&(const Bits& other) const {
return Bits(value & other.value);
}
};
14.2 重载 |
Bits operator|(const Bits& other) const {
return Bits(value | other.value);
}
14.3 重载 ^
Bits operator^(const Bits& other) const {
return Bits(value ^ other.value);
}
14.4 重载 ~
Bits operator~() const {
return Bits(~value);
}
14.5 重载位移 <<、>>
如果不是输出输入,而是位移:
Bits operator<<(int k) const {
return Bits(value << k);
}
Bits operator>>(int k) const {
return Bits(value >> k);
}
使用:
Bits a(3);
Bits b = a << 2;
15. 逗号运算符重载 ,
这个可以重载,但非常少用,也不推荐初学者使用。
class Number {
private:
int value;
public:
Number(int value = 0) : value(value) {}
Number operator,(const Number& other) const {
return other;
}
};
使用:
Number a(1), b(2);
Number c = (a, b);
这会返回 b。
但是逗号重载很容易让代码变难懂,所以考试一般不会重点考。
16. 内存分配运算符重载 new 和 delete
这个属于比较高级的重载。
16.1 重载 operator new
class MyClass {
public:
void* operator new(size_t size) {
cout << "custom new, size = " << size << endl;
return ::operator new(size);
}
void operator delete(void* ptr) {
cout << "custom delete" << endl;
::operator delete(ptr);
}
};
使用:
MyClass* p = new MyClass;
delete p;
输出可能是:
custom new, size = ...
custom delete
16.2 重载数组版本
void* operator new[](size_t size) {
return ::operator new[](size);
}
void operator delete[](void* ptr) {
::operator delete[](ptr);
}
这个一般了解即可。
17. 取地址运算符重载 &
这个也可以重载,但非常少用。
class MyClass {
public:
MyClass* operator&() {
cout << "custom address operator" << endl;
return this;
}
};
使用:
MyClass a;
MyClass* p = &a;
实际开发中不太建议随便重载 &,因为会破坏大家对取地址的直觉。
18. 不能重载的运算符
这些不能重载:
.
.*
::
?:
sizeof
typeid
alignof
常见记住这几个:
| 运算符 | 含义 | 能否重载 |
|---|---|---|
. | 成员访问 | 不能 |
:: | 作用域解析 | 不能 |
?: | 三目运算符 | 不能 |
sizeof | 求大小 | 不能 |
typeid | 类型信息 | 不能 |
19. 必须写成成员函数的运算符
有些运算符必须写在类里面,不能写成普通全局函数。
| 运算符 | 说明 |
|---|---|
= | 赋值 |
[] | 下标 |
() | 函数调用 |
-> | 成员访问 |
| 类型转换运算符 | 例如 operator int() |
例如:
T& operator=(const T& other);
R& operator[](int index);
bool operator()(int x) const;
T* operator->();
operator int() const;
20. 通常写成全局函数的运算符
最典型的是:
<<
>>
因为你希望写:
cout << obj;
cin >> obj;
左边是 cout 或 cin,不是你的对象。
所以一般写成:
friend ostream& operator<<(ostream& out, const T& obj);
friend istream& operator>>(istream& in, T& obj);
21. 完整综合例子:三维向量类
这个例子把常见重载都放进去了。
#include <iostream>
using namespace std;
class Vector3 {
private:
int x, y, z;
public:
Vector3(int x = 0, int y = 0, int z = 0)
: x(x), y(y), z(z) {}
Vector3 operator+(const Vector3& other) const {
return Vector3(x + other.x, y + other.y, z + other.z);
}
Vector3 operator-(const Vector3& other) const {
return Vector3(x - other.x, y - other.y, z - other.z);
}
Vector3 operator-() const {
return Vector3(-x, -y, -z);
}
Vector3 operator*(int k) const {
return Vector3(x * k, y * k, z * k);
}
Vector3& operator+=(const Vector3& other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}
Vector3& operator-=(const Vector3& other) {
x -= other.x;
y -= other.y;
z -= other.z;
return *this;
}
bool operator==(const Vector3& other) const {
return x == other.x && y == other.y && z == other.z;
}
bool operator!=(const Vector3& other) const {
return !(*this == other);
}
int& operator[](int index) {
if (index == 0) return x;
if (index == 1) return y;
return z;
}
const int& operator[](int index) const {
if (index == 0) return x;
if (index == 1) return y;
return z;
}
friend ostream& operator<<(ostream& out, const Vector3& v);
friend istream& operator>>(istream& in, Vector3& v);
};
ostream& operator<<(ostream& out, const Vector3& v) {
out << "(" << v.x << ", " << v.y << ", " << v.z << ")";
return out;
}
istream& operator>>(istream& in, Vector3& v) {
in >> v.x >> v.y >> v.z;
return in;
}
int main() {
Vector3 a(1, 2, 3);
Vector3 b(4, 5, 6);
cout << a + b << endl;
cout << a - b << endl;
cout << -a << endl;
a += b;
cout << a << endl;
a[0] = 100;
cout << a << endl;
if (a != b) {
cout << "not equal" << endl;
}
return 0;
}
22. 最重要的考试模板总结
+
T operator+(const T& other) const {
return T(...);
}
+=
T& operator+=(const T& other) {
// 修改当前对象
return *this;
}
==
bool operator==(const T& other) const {
return ...;
}
<
bool operator<(const T& other) const {
return ...;
}
<<
friend ostream& operator<<(ostream& out, const T& obj);
ostream& operator<<(ostream& out, const T& obj) {
out << ...;
return out;
}
>>
friend istream& operator>>(istream& in, T& obj);
istream& operator>>(istream& in, T& obj) {
in >> ...;
return in;
}
[]
R& operator[](int index) {
return ...;
}
const R& operator[](int index) const {
return ...;
}
()
bool operator()(const int& x) const {
return ...;
}
前置 ++
T& operator++() {
// 修改
return *this;
}
后置 ++
T operator++(int) {
T old = *this;
// 修改
return old;
}
=
T& operator=(const T& other) {
if (this == &other) {
return *this;
}
// 释放旧资源
// 申请新资源
// 拷贝内容
return *this;
}
23. 最好记的一句话
运算符重载按用途分:
+ - * / // 返回新对象,一般不修改自己
+= -= *= /= // 修改自己,返回 *this
== < > // 返回 bool
<< >> // 输入输出,通常写 friend 全局函数
[] // 像数组一样访问,返回引用
() // 像函数一样调用,仿函数
++ -- // 分前置和后置
= // 深拷贝时非常重要
-> * // 智能指针、迭代器常用
你现在最该优先掌握的是:
operator+
operator+=
operator==
operator<
operator<<
operator>>
operator[]
operator()
operator++
operator=
这几个会了,学校 C++ 类与对象、继承、实验题里的运算符重载基本就够用了。