C++ 运算符重载:让自己的类也能用 +、==、<<
运算符重载(operator overloading)就是给自定义类型(类 / 结构体)定义运算符的行为。默认情况下,int a + int b 这种内置类型能 +,但 MyClass a + MyClass b 编译器不知道怎么办——除非你告诉它「+ 在我的类上应该怎么算」。
一、为什么需要运算符重载
举一个简单的例子:复数类想实现两个复数相加:
1 | struct Complex { |
如果直接写 c1 + c2,编译报错:no match for 'operator+' ...。
通过运算符重载,可以写成像内置类型一样自然:
1 | Complex c3 = c1 + c2; // 像 int 那样用 |
二、运算符重载的语法
两种形式:
形式 1:成员函数
1 | struct Complex { |
调用 c1 + c2 时,编译器翻译成 c1.operator+(c2)。
形式 2:全局函数(通常配合友元)
1 | struct Complex { |
调用 c1 + c2 时,编译器翻译成 operator+(c1, c2)。
三、完整示例:复数类
1 |
|
四、可以重载的运算符
| 类别 | 运算符 | ||
|---|---|---|---|
| 算术 | + - * / % |
||
| 比较 | == != < > <= >= |
||
| 逻辑 | `&& \ | \ | ` (不推荐,会失去短路求值) |
| 位运算 | `& \ | ^ ~ << >>` | |
| 自增减 | ++ --(分前置、后置) |
||
| 赋值 | `= += -= *= /= %= &= \ | = ^= <<= >>=` | |
| 下标 | [] |
||
| 调用 | () |
||
| 成员访问 | -> |
||
| 类型转换 | operator T() |
||
| 流 | << >>(必须用友元) |
⚠️ 不能重载的运算符:
::(作用域)、.(成员访问)、.*、?:(三目)、sizeof、类型相关运算符。重载也不会改变优先级和结合性。
五、关键细节
5.1 重载为成员函数 vs 全局函数
| 情形 | 推荐形式 |
|---|---|
单目运算符、=、()、[]、-> |
必须成员函数 |
<< >> 流运算符 |
必须友元全局函数(因为左侧是 ostream) |
对称二元运算符(+ - * /) |
都可以;需要访问私有成员时用友元 |
5.2 operator= 拷贝赋值与移动赋值
如果类里有指针 / 资源,要重载拷贝赋值和移动赋值(参考 RAII、智能指针那一篇):
1 | class Buffer { |
Rule of Three/Five:如果需要析构、拷贝构造、拷贝赋值三者之一,通常另外两个也需要;C++11 之后加上移动版本就是「Rule of Five」。
5.3 前置 ++ 和后置 ++ 的区分
1 | class Counter { |
前置版本返回引用,后置版本返回值——所以前置
++比后置++略快(少一次拷贝)。循环里建议写++it而不是it++。
5.4 函数调用运算符 operator():让对象像函数
1 | struct Add { |
这种重载了 () 的对象叫函数对象(仿函数),STL 算法里常用。
5.5 类型转换运算符 operator T()
1 | class Fraction { |
容易被滥用,导致隐式转换意外发生。优先用显式
explicit operator T()(C++11 起)。
六、友元函数与友元类
类内声明 friend,让外部函数 / 类能访问本类的 private / protected 成员。友元关系不传递、不继承。
1 | class Matrix; |
友元会破坏封装,慎用。常见合理场景:流运算符
<<>>、需要对称访问两对象的运算符。
七、易错点清单
- 重载
&&||会失去短路求值:这两个运算符的求值顺序在 C++ 里是被语言规定的,重载后变成普通函数调用,不再短路。除非你完全知道在做什么,否则不要重载。 - 重载
,也会失去顺序保证。同理避免。 operator=没处理自赋值:a = a;不检查this == &other会把自己 delete 掉再拷。- 重载流运算符忘了返回
ostream&:return os;漏掉就断了链式调用,cout << a << b;编译报错。 - 忘了
const修饰成员运算符:成员运算符没加const,就不能在const对象上用。 - 重载运算符改变了语义:比如把
+写成"相减"。会极大降低可读性,重载后语义应当和内置运算符一致。
八、什么时候不要重载
- 内置类型上重载 —— 不允许;
- 没有自然语义时(比如让
+表示"输出")—— 可读性灾难; - 团队 / 项目里大家都不知道你重载了什么 —— 沟通成本高。
九、极简速记
| 场景 | 推荐写法 |
|---|---|
| 一般二元算术 | 成员函数 operator+ |
<< / >> 流 |
友元全局函数 |
| 自增减 | 前置 T& operator++();后置 T operator++(int) |
| 下标 / 调用 | 成员函数 operator[] / operator() |
| 赋值 | 必须成员函数,注意自赋值 |
| 仿函数 | 成员函数 operator(),广泛用于 STL |
十、小结
- 运算符重载 = 给自定义类型定义运算符行为,让代码更自然;
=()[]->必须成员函数,流运算符必须友元;- 写资源管理类时遵守 Rule of Three/Five;
- 不要重载
&&||,,会破坏语言本身的求值规则; - 重载运算符的语义要和内置运算符保持一致,不要"挂羊头卖狗肉"。
