让你自己定义的类型也能用 +、<、== 这些符号——写出来的代码就像操作内置类型一样自然。竞赛中最常用的是 operator<,配合 sort 实现自定义排序。
C++ 内置的 int、double 等类型可以直接用 +、-、< 这些符号运算。但如果你自己定义了一个 Point 类(表示二维坐标点),想把两个点相加,默认是不能直接写 p1 + p2 的——编译器不知道两个 Point 加在一起意味着什么。
运算符重载就是告诉编译器:当这个符号作用于我的类型时,应该做什么。
p1 + p2p1 + p2重载运算符用关键字 operator 后跟要重载的符号来定义,写在类内部:
| 1 | class MyClass { |
| 2 | public: |
| 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 则保证函数不会修改传入的参数。这是重载运算符的惯用写法。
以二维坐标点 Point 为例,重载 + 让两个点可以直接相加:
| 1 | struct 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 | |
| 14 | int 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 | } |
const:这表示这个函数不会修改对象自身的数据。对于只做计算、不改状态的运算符(+、==、< 等),都应该加上这个 const。const Point?第 5 行返回的是一个临时对象(p1 + p2 算出来的新 Point),如果返回类型是非 const 的 Point,像 (p1 + p2) = p3; 这种把值赋给临时结果的写法居然能编译通过——这通常不是本意,很可能是笔误。把返回类型写成 const Point 能让编译器直接拦下这种误用。这是比较严谨的写法,竞赛代码里很少讲究到这一步,了解即可。这是竞赛中运算符重载最常见、最实用的场景。std::sort 在排序时会调用 < 来比较元素大小。只要你的类重载了 operator<,就能直接用 sort 排序,无需额外写比较函数。
| 1 | struct 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 | |
| 15 | int 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 内部每次比较两个元素时,都会调用你写的 operator<。sort 就能帮你排好整个序列。
priority_queue 默认也用 operator< 来比较优先级。重载了 operator<,自定义类型就能直接放进优先队列,无需额外写仿函数。
重载 << 之后,就可以直接用 cout << p 打印自定义类型,调试起来非常方便。
这个运算符比较特殊,必须写成类外的全局函数,因为左边是 ostream 而不是你的类:
| 1 | struct Point { |
| 2 | int x, y; |
| 3 | Point(int x, int y) : x(x), y(y) {} |
| 4 | }; |
| 5 | |
| 6 | // 写在类外,第一个参数是 ostream& |
| 7 | ostream& operator<<(ostream& os, const Point& p) { |
| 8 | os << "(" << p.x << ", " << p.y << ")"; |
| 9 | return os; // 返回 os 以支持链式调用 |
| 10 | } |
| 11 | |
| 12 | int 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 不在本教程的讲解范围内,感兴趣可以自行查阅。| 运算符 | 常见用途 | 写在类内 / 类外 |
|---|---|---|
| + - * / | 向量、矩阵、复数等数学对象的运算 | 类内(推荐) |
| < > == != | 配合 sort、priority_queue、set 的比较逻辑 | 类内(推荐) |
| << >> | cout / cin 输出输入自定义类型,调试常用 | 必须写在类外 |
| [] | 自定义容器的下标访问 | 类内 |
| ++ -- | 迭代器等场景的自增自减 | 类内 |
** 表示乘方)。* 永远比 + 先算。::、.、?:、sizeof 这几个特殊运算符——它们涉及的是语言本身最核心的语法规则和类型系统(比如 . 决定了"怎么访问成员"这件事本身),如果允许重载,会破坏语言的基本规则,所以 C++ 标准直接禁止了它们。
operator符号 定义运算符的行为,让自定义类型像内置类型一样好用。operator<,写好之后 sort 和 priority_queue 都能直接用。operator<< 需要写在类外,返回 ostream& 以支持链式输出。