← 目录 / 第十二章 · 类与面向对象 / 12.5 运算符重载

12.5 运算符重载

让你自己定义的类型也能用 +、<、== 这些符号——写出来的代码就像操作内置类型一样自然。竞赛中最常用的是 operator<,配合 sort 实现自定义排序。

本页目录
12.5.1 什么是运算符重载

C++ 内置的 int、double 等类型可以直接用 +、-、< 这些符号运算。但如果你自己定义了一个 Point 类(表示二维坐标点),想把两个点相加,默认是不能直接写 p1 + p2 的——编译器不知道两个 Point 加在一起意味着什么。

运算符重载就是告诉编译器:当这个符号作用于我的类型时,应该做什么。

没有重载时
p1 + p2
❌ 编译错误
编译器不知道怎么加两个 Point
→
重载后
重载之后
p1 + p2
✅ 正常运行
返回新 Point(x1+x2, y1+y2)
12.5.2 基本语法

重载运算符用关键字 operator 后跟要重载的符号来定义,写在类内部:

C++ · 运算符重载的基本结构
1class MyClass {
2public:
3 // 返回值类型 operator符号 (参数) { ... }
4 MyClass operator+(const MyClass& other) {
5 // 定义 + 的行为,返回运算结果
6 }
7 bool operator<(const MyClass& other) {
8 // 定义 < 的行为,返回 true/false
9 }
10};
💡
参数为什么用 const &?
写 const MyClass& other 而不是 MyClass other,是因为引用传参不需要复制对象(更快),const 则保证函数不会修改传入的参数。这是重载运算符的惯用写法。
12.5.3 重载加法运算符

以二维坐标点 Point 为例,重载 + 让两个点可以直接相加:

C++ · 重载 operator+
1struct Point {
2 int x, y;
3 Point(int x, int y) : x(x), y(y) {}
4
5 Point operator+(const Point& other) const {
6 return Point(x + other.x, y + other.y);
7 }
8
9 bool operator==(const Point& other) const {
10 return x == other.x && y == other.y;
11 }
12};
13
14int main() {
15 Point p1(1, 2), p2(3, 4);
16 Point p3 = p1 + p2; // → Point(4, 6),调用 operator+
17 cout << (p1 == p2); // → 0 (false),调用 operator==
18}
📌
注意第 5 行函数末尾的 const:这表示这个函数不会修改对象自身的数据。对于只做计算、不改状态的运算符(+、==、< 等),都应该加上这个 const。
🎯
进阶:为什么有的教程把返回值也写成 const Point?第 5 行返回的是一个临时对象(p1 + p2 算出来的新 Point),如果返回类型是非 const 的 Point,像 (p1 + p2) = p3; 这种把值赋给临时结果的写法居然能编译通过——这通常不是本意,很可能是笔误。把返回类型写成 const Point 能让编译器直接拦下这种误用。这是比较严谨的写法,竞赛代码里很少讲究到这一步,了解即可。
12.5.4 重载比较运算符与 sort

这是竞赛中运算符重载最常见、最实用的场景。std::sort 在排序时会调用 < 来比较元素大小。只要你的类重载了 operator<,就能直接用 sort 排序,无需额外写比较函数。

C++ · operator< 配合 sort 多关键字排序
1struct Student {
2 string name;
3 int score;
4 int age;
5
6 bool operator<(const Student& other) const {
7 if (score != other.score) // 第一关键字:分数降序
8 return score > other.score;
9 if (age != other.age) // 第二关键字:年龄升序
10 return age < other.age;
11 return name < other.name; // 第三关键字:姓名字典序
12 }
13};
14
15int main() {
16 vector<Student> v = {
17 {"Alice", 95, 16},
18 {"Bob", 82, 17},
19 {"Carol", 95, 15},
20 };
21 sort(v.begin(), v.end()); // 自动调用 operator<
22 // 排序结果:Carol(95,15) → Alice(95,16) → Bob(82,17)
23}
sort(v.begin(), v.end()) 遇到需要比较两个元素时 自动调用 operator<(a, b) 你定义的比较逻辑,返回 true/false 排序前 Alice 95 16 Bob 82 17 Carol 95 15 排序后 Carol 95 15 Alice 95 16 Bob 82 17 分数相同时比年龄,年龄也相同时比姓名——多关键字排序只需在 operator< 里依次判断
sort 内部每次比较两个元素时,都会调用你写的 operator<。
你只需要定义"什么叫做 a 比 b 小",sort 就能帮你排好整个序列。
🏆
priority_queue 也一样:priority_queue 默认也用 operator< 来比较优先级。重载了 operator<,自定义类型就能直接放进优先队列,无需额外写仿函数。
12.5.5 重载流输出运算符

重载 << 之后,就可以直接用 cout << p 打印自定义类型,调试起来非常方便。

这个运算符比较特殊,必须写成类外的全局函数,因为左边是 ostream 而不是你的类:

C++ · 重载 operator<<
1struct Point {
2 int x, y;
3 Point(int x, int y) : x(x), y(y) {}
4};
5
6// 写在类外,第一个参数是 ostream&
7ostream& operator<<(ostream& os, const Point& p) {
8 os << "(" << p.x << ", " << p.y << ")";
9 return os; // 返回 os 以支持链式调用
10}
11
12int main() {
13 Point p(3, 4);
14 cout << p << endl; // 输出:(3, 4)
15 cout << p << " and " << p; // 链式调用也正常
16}
⚠️
如果成员是 private 呢?上面例子里 Point 是 struct,成员默认 public,所以第 8 行 operator<< 才能直接写 p.x、p.y——因为它是类外的全局函数(见上方说明),不属于类的成员。如果把 x、y 换成 private,这里就会编译报错,因为全局函数不具备访问私有成员的权限。常见的绕开办法是给类另外提供 public 的 getter(见 12.3 节),或者使用 friend(友元)关键字单独为这个全局函数开一个"后门"——friend 不在本教程的讲解范围内,感兴趣可以自行查阅。
12.5.6 使用规则与注意事项
运算符常见用途写在类内 / 类外
+ - * / 向量、矩阵、复数等数学对象的运算 类内(推荐)
< > == != 配合 sort、priority_queue、set 的比较逻辑 类内(推荐)
<< >> cout / cin 输出输入自定义类型,调试常用 必须写在类外
[] 自定义容器的下标访问 类内
++ -- 迭代器等场景的自增自减 类内
⚠️
三条不能打破的规则:
① 只能重载已有的运算符,不能发明新符号(比如 ** 表示乘方)。
② 不能改变运算符的优先级和结合性——* 永远比 + 先算。
③ 不能重载 ::、.、?:、sizeof 这几个特殊运算符——它们涉及的是语言本身最核心的语法规则和类型系统(比如 . 决定了"怎么访问成员"这件事本身),如果允许重载,会破坏语言的基本规则,所以 C++ 标准直接禁止了它们。
📖
本节小结
・用 operator符号 定义运算符的行为,让自定义类型像内置类型一样好用。
・竞赛中最常用的是 operator<,写好之后 sort 和 priority_queue 都能直接用。
・operator<< 需要写在类外,返回 ostream& 以支持链式输出。
・不能改变优先级,不能创造新符号。