面向对象(四):运算符重载

原理

运算符重载的本质

a + b 等价于 a.operator+(b)(成员形式)或 operator+(a, b)(非成员形式)。编译器遇到表达式时,先进行名称查找找到候选的 operator+ 函数,然后通过重载决议选择最佳匹配。

成员 vs 友元的底层区别

成员形式和友元形式在汇编层产生完全相同的代码——区别仅在于参数的语义标签:

; 成员形式 a+b → call X::operator+ , rdi=&a, rsi=&b
; 友元形式 a+b → call operator+(X,X), rdi=&a, rsi=&b
; 函数内部的指令完全相同
```asm
 
决策表:
 
| 场景 | 选成员 | 选友元 | 原因 |
|---|---|---|---|
| 左操作数确定是 `*this` | 可 | 可 | 都可以,成员更自然 |
| 左操作数不是本类类型(如 `5+obj`, `cout<<obj`) | 不可 | **必须** | 成员函数要求左操作数有对应成员 |
| `= [] () ->` | **必须** | 不可 | C++ 标准强制规定 |
 
### 三个不变原则
 
1. **优先级不变**:优先级是语法解析阶段就确定的,重载决议发生在语义分析阶段——远在 AST 构建之后
2. **结合性不变**:`a + b + c` 永远等价于 `(a + b) + c`(左结合运算符)
3. **操作数个数不变**:二元运算符永远接收两个操作数
 
### 返回类型的决策依据
 
| 返回类型 | 典型运算符 | 汇编特征 |
|---|---|---|
| `T`(值返回) | `+ - * /` | `call new; call 构造; ret`,创建新对象 |
| `T&`(引用返回) | `= += -= <<` | `mov rax, rdi; ret`,直接返回 this,零开销 |
| `bool` | `== != < >` | `xor eax, eax; cmp; sete al; ret` |
 
### 前置 vs 后置自增的汇编
 
```asm
; 前置 ++c — operator++() → Counter&
inc dword [rdi] ; 直接自增(1 条指令)
mov rax, rdi ; 返回 this(1 条指令)
 
; 后置 c++ — operator++(int) → Counter
mov eax, [rdi] ; 保存旧值
inc dword [rdi] ; 自增
; 可能还有拷贝构造 → 至少多一次拷贝操作
```cpp
 
后置版本的 `int` 参数是语法标记,由编译器自动传入 `0`,仅用于重载决议中区分前后置。前置版本返回 `T&`(修改后自身),后置版本返回 `T`(旧值副本)。
 
### 转换构造函数与运算符的交互
 
转换构造函数(非 explicit 的单参数构造函数)允许隐式类型转换参与运算符匹配:
 
```cpp
Complex a(1,2);
Complex b = a + 3.5; // 3.5 → Complex(3.5, 0.0),然后 operator+ 匹配
Complex c = 3.5 + a; // 编译错误!3.5 是 double,没有 operator+(Complex)
```asm
 
`explicit` 关键字在重载决议中阻断隐式转换路径,编译器跳过该构造函数,只保留显式构造的路径。
 
### ADL 让运算符跨命名空间工作
 
`a + b` 中如果 a 和 b 的类型在命名空间 `N` 中,ADL 自动把 `N` 加入 `operator+` 的搜索范围。不用 `using namespace N;` 就能使用运算符——这是运算符重载自然语法的关键设计。
 
---
 
## 语法
 
### 算术与赋值运算符
 
```cpp
class Vec {
 double x, y;
public:
 Vec operator+(const Vec& v) const { return {x+v.x, y+v.y}; }
 Vec& operator+=(const Vec& v) { x+=v.x; y+=v.y; return *this; }
 // 通常用 += 实现 +
 friend Vec operator+(const Vec& a, const Vec& b) { Vec r(a); r+=b; return r; }
};
```asm
 
### 比较运算符
 
```cpp
bool operator==(const Vec& v) const { return x==v.x && y==v.y; }
bool operator!=(const Vec& v) const { return !(*this == v); }
bool operator<(const Vec& v) const { return x < v.x || (x==v.x && y<v.y); }
// >, <=, >= 通过 < 和 == 实现
```asm
 
### 自增自减
 
```cpp
Vec& operator++() { ++x; return *this; } // 前置:返回引用
Vec operator++(int) { Vec t(*this); ++x; return t; } // 后置:返回值
```asm
 
### 下标与函数调用
 
```cpp
T& operator[](size_t i) { return data[i]; } // 必须成员
void operator()(int x) { /* 函数对象 */ } // 必须成员
```asm
 
### 流运算符(必须友元)
 
```cpp
friend std::ostream& operator<<(std::ostream& os, const Vec& v) {
 return os << "(" << v.x << ", " << v.y << ")";
}
```asm
 
### 类型转换运算符
 
```cpp
operator double() const { return length; } // 隐式转换(不推荐)
explicit operator int() const { return (int)x; } // 显式转换(C++11,推荐)
explicit operator bool() const { return valid; } // 条件判断可用
```asm
 
### 字面量运算符(C++11
 
```cpp
Distance operator"" _km(long double val) { return Distance(val * 1000.0); }
auto d = 5.0_km; // Distance(5000.0)
```cpp
 
### C++20 太空船运算符
 
```cpp
auto operator<=>(const T&) const = default;
// 编译器自动生成 ==, !=, <, >, <=, >= 全部六个运算符
```asm
 
---
 
## 实践
 
**练习**:日期处理题(日期类与比较运算符),排序题题(结构体排序与运算符重载)。