运算符重载=让 + - * == << 这些符号,对你自己的类型也有意义。本质上,运算符就是一种"长得特别的函数名":a + b 可以理解成 operator+(a, b)。它不增加语言的能力(函数也能干同样的事),它改变的是代码的可读性——让 a + b 这种"一看就懂"的写法,落在你自己造的类上。
上一节刚讲完"同名函数会被隐藏"(11.1.5),本节换个方向:不再纠结"谁藏谁",而是主动给符号"起名字"。重载运算符和 6.5 的重载函数是同一套机制(同名、不同参数,编译期选),只是名字由 operator符号 构成;它还要用到上章的 10.5 友元(因为大多数运算符重载要写"非成员函数"),以及 10.2 构造函数(因为返回值往往是"造一个新对象")。
一、运算符本来就是"函数"
C++ 里 a + b 这句话,编译器内部看到的是:
a + b ≡ operator+(a, b)
区别只是"写法":一个用符号、一个用名字。所以:
| 写法 | 展开理解 |
|---|---|
a + b |
operator+(a, b) |
a == b |
operator==(a, b) |
cout << c |
operator<<(cout, c) |
v[i] |
operator[](v, i)(这个必须是成员函数,11.2.2) |
++a |
operator++(a) |
内置类型(int、double)的这套"函数"是语言自带的;你要做的事,是给"自己造的类型"补上这些函数——于是它们也就成了"运算符的一员"。
"重载"这个词指的是:+ 这个符号在 int 上、在 double 上、在你的 Complex 上,都有一套实现——同一个符号,不同参数类型,走不同代码。这就是 6.5 说的"重载"。
二、三条底线(先立规矩)
| 底线 | 说明 |
|---|---|
| 不能发明新符号 | 没有 **、没有 <>、没有 ===。只能用语言里已有的那些 |
| 不能改变优先级和结合性 | a + b * c 永远先算 *(3.2 的知识照旧有效),你改不了 |
| 至少要有一个操作数是自定义类型 | 不能去改 int + int 的含义(那叫改语言规则,不叫重载) |
再加一条:重载运算符是为了让代码更好懂,不是为了炫技。 如果你写的 + 做的是减法,那这个类没法跟人合作。
三、为什么它值得单独一章
因为它让自定义类型"长得像内置类型":
// 没有重载运算符
Complex c = addComplex(multiplyComplex(a, b), c2);
int i = getElement(getArray(v), 3);
// 有了重载运算符
Complex c = a * b + c2;
int i = v[3];
读代码的人不用先查"这个函数是干什么的"——符号本身就表达了意图。这就是"重载运算符的必要性"(11.2.1 展开讲)。
四、本章路线图
| 节 | 讲什么 |
|---|---|
| 11.2.1 | 必要性:不用重载运算符有多啰嗦,用了之后有多自然 |
| 11.2.2 | 形式与规则:两种写法(成员 / 友元)、哪些必须用成员、五个不能重载的符号;含实例04 |
| 11.2.3 | 具体的运算:+ = == ++ [] << 各自怎么重载,返回值写 T 还是 T& |
| 11.2.4 | 转换运算符:operator double()——让自定义类型能"变成"内置类型 |
五、动手 Demo:让"钱"能用 +
#include <iostream>
using namespace std;
class Money {
private:
long yuan; // 元
int fen; // 分
public:
Money(long y = 0, int f = 0) : yuan(y), fen(f) {
yuan += fen / 100; // 规范化:把"分"进位到"元"
fen %= 100;
}
// ★ 让 Money 也能用 + 号
Money operator+(const Money &other) const {
return Money(yuan + other.yuan, fen + other.fen);
}
void show() const {
cout << yuan << "."
<< (fen < 10 ? "0" : "") << fen << " 元" << endl;
}
};
int main() {
Money a(12, 80), b(5, 45);
Money c = a + b; // ← 从前这句编译不过,现在可以了
a.show();
b.show();
c.show();
return 0;
}
看到什么算成功:
12.80 元
5.45 元
18.25 元
读结果:12.80 + 5.45,两个零头加起来是 80 + 45 = 125 分,构造函数里那句"规范化"自动进位成 1 元 25 分,于是 18.25。
30 秒实验:把 Money operator+(const Money &other) const { ... } 整段注释掉,重新编译 → Money c = a + b; 报错:no match for 'operator+' (operand types are 'Money' and 'Money')。
- 注意报错里的 operator+——编译器就是这么称呼那个符号的。
- 把注释去掉,就好。 你给出的那几行,就是 + 这个符号在 Money 上的"实现"。
六、常见误区
- "运算符重载是给语言加新功能"——不是。它只是让"已有的符号"在"你的类型"上有意义,表达力上函数完全能做到(
add(a,b))。重载运算符买的只有可读性,代价是你得守"符号的直觉含义"。 - "我想造个
**表示乘方"——不行。不能发明新符号。乘方得写成函数(pow(a, b)或者自己写a.pow(b))。 - "重载了
+,+=就自动有了"——没有。每个运算符各自独立重载。(可以自己用+=去实现+,那是你的选择,不是语言帮的忙——11.2.3 会示范。) - "重载运算符能改变优先级"——不能。
a + b * c在任何类型上都是先乘后加。如果你需要别的运算顺序,只能靠加括号。 - "运算符重载越多越好"——不是。只重载"符号含义天然贴切"的那些(
+是合并、==是比较、<<是输出)。给类重载-让它做"打印",同事会想打你。
自测三问(点开对答案)
1. `a + b` 在编译器眼里是什么?——**`operator+(a, b)`**。**运算符无非是"长得特别的函数名",`operator` 关键字就是用来给它们"起名"的。** 2. 运算符重载能改变哪些东西?不能改变哪些?——**能改变**:这个符号对**你的类型**的具体含义(实现)。**不能改变**:优先级、结合性、操作数个数,也**不能发明新符号**。 3. 运算符重载真正的价值是什么?——**可读性**。它让自定义类型写起来像内置类型(`a * b + c` 一眼看懂),**能力上并不比函数多**。- ☐ 11.2.1 重载运算符的必要性(下一节)
相关笔记
- 重载运算符的必要性 —— 下一节:不用重载有多啰嗦
- 子类隐藏父类的成员函数(含实例03) —— 上一节:同名函数在父子类间的坑
- 重载函数 —— 重载的本义:同名、不同参数
- 结合性和优先级 —— 优先级是语言定的,改不了
- 友元 —— 运算符重载是友元的主战场
- 🔷 C++基础 · 索引 —— 全课路线图