🔷 第十一章 继承与派生
📄 一句话概括

运算符重载=让 + - * == << 这些符号,对你自己的类型也有意义。本质上,运算符就是一种"长得特别的函数名":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 上的"实现"。

六、常见误区

  1. "运算符重载是给语言加新功能"——不是。它只是让"已有的符号"在"你的类型"上有意义,表达力上函数完全能做到(add(a,b))。重载运算符买的只有可读性,代价是你得守"符号的直觉含义"。
  2. "我想造个 ** 表示乘方"——不行。不能发明新符号。乘方得写成函数(pow(a, b) 或者自己写 a.pow(b))。
  3. "重载了 +,+= 就自动有了"——没有。每个运算符各自独立重载。(可以自己用 += 去实现 +,那是你的选择,不是语言帮的忙——11.2.3 会示范。)
  4. "重载运算符能改变优先级"——不能。a + b * c 在任何类型上都是先乘后加。如果你需要别的运算顺序,只能靠加括号。
  5. "运算符重载越多越好"——不是。只重载"符号含义天然贴切"的那些(+ 是合并、== 是比较、<< 是输出)。给类重载 - 让它做"打印",同事会想打你。
自测三问(点开对答案) 1. `a + b` 在编译器眼里是什么?——**`operator+(a, b)`**。**运算符无非是"长得特别的函数名",`operator` 关键字就是用来给它们"起名"的。** 2. 运算符重载能改变哪些东西?不能改变哪些?——**能改变**:这个符号对**你的类型**的具体含义(实现)。**不能改变**:优先级、结合性、操作数个数,也**不能发明新符号**。 3. 运算符重载真正的价值是什么?——**可读性**。它让自定义类型写起来像内置类型(`a * b + c` 一眼看懂),**能力上并不比函数多**。

相关笔记