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

多重继承=一个类同时继承多个父类:class 子类 : public A, public B { };。它让"一体机既是打印机又是扫描仪"这种真实世界的关系能直接写出来。代价是两个独有的麻烦:① 二义性(两个父类有同名成员,叫谁都行 → 编译器不干);② 菱形继承(两条路径通向同一个祖先 → 祖先的成员在对象里有两份)。C++ 是少数支持多重继承的主流语言——这既是它的强大,也是它的麻烦来源。

📝 本节和前面几节的关系

前面 11.1 讲的都是"一个父类"。本节把继承的维度加一——于是 11.1.2 的权限表、11.1.3 的构造顺序表,都要重新确认一遍(答案是:规律不变,但"多个父类之间谁先谁后"成了一件要操心的事)。本节还会用到 9.3.2 类图的知识:菱形继承在 UML 里一眼就能看出来,那是一个典型的"设计出了问题"的信号。

一、语法:逗号分隔多个父类

class 子类名 : public 父类1, public 父类2, public 父类3 {
    // 新增成员
};

三条语法注意点:

注意点 说明
每个父类前都要写继承方式 漏写的话,class 默认 private——而多重继承里最容易漏的就是第二个、第三个前面的 public(11.1.1 的坑在多重继承里翻倍)
顺序有意义 构造顺序按这里的书写顺序来(11.3.3 会专门做实例验证)
父类不能重复写 class C : public A, public A 是错的(同一个直接基类只能出现一次)

记忆口诀:一个冒号,一列基类,每个都带 public。

二、它为什么存在:现实里的"既是……又是……"

多重继承描述的是"一个东西同时具备两种身份":

现实 多重继承的样子
一体机 既是打印机,又是扫描仪
在职研究生 既是学生,又是员工
水陆两栖车 既是车,又是船
智能音箱 既是音箱,又是联网设备

没有多重继承时会怎样?要么放弃一种身份,要么把两种身份的能力都复制一份到新类里(回到 11.1 开头那个"重复代码"的泥潭)。

三、它有争议:两个独有的麻烦

麻烦 说什么 在哪一节细讲
二义性(ambiguity) 两个父类有同名成员时,obj.x 编译器不知道你要哪个,直接报错 11.3.2
菱形继承(diamond) A → B、A → C、B+C → D:D 里有两份 A 的数据,白白多占内存、还容易改错一份 11.3.2 + 11.4.3(虚继承)

影响有多大? 大到有些语言直接禁止多重继承(Java 只允许"多继承接口"、C# 同理)。C++ 保留了它,于是这个麻烦就交给使用者来管。

工程上的态度:"多重继承是个好工具,但要少用"。用到时,优先让多个父类彼此毫无关系(像"打印机 + 扫描仪"),避免它们有共同祖先(菱形)。如果绕不开共同祖先,用虚继承(11.4.3)。

四、本章路线图

节 讲什么
11.3.1 定义:语法、对象布局、怎么指名调用某个父类的成员
11.3.2 二义性:同名成员的冲突;菱形继承的完整现场
11.3.3 构造顺序:多个父类"谁先构造",以及析构怎么反过来;含实例05

五、动手 Demo:一台"一体机"

#include <iostream>
using namespace std;

class Printer {
public:
    void print() const { cout << "正在打印……" << endl; }
};

class Scanner {
public:
    void scan() const { cout << "正在扫描……" << endl; }
};

// ★ 一体机:既是打印机,又是扫描仪
class Copier : public Printer, public Scanner {
public:
    void copy() const {
        scan();                 // 来自 Scanner
        print();                // 来自 Printer
        cout << "复印完成" << endl;
    }
};

int main() {
    Copier m;
    m.print();                  // 父类 1 的能力
    m.scan();                   // 父类 2 的能力
    m.copy();                   // 自己的能力(内部用了两个父类)
    return 0;
}

看到什么算成功:

正在打印……
正在扫描……
正在扫描……
正在打印……
复印完成

读结果:

30 秒实验:给 Scanner 也加一个 void print()(意思是"扫描仪也能打印状态"),重新编译。

class Scanner {
public:
    void scan() const { cout << "正在扫描……" << endl; }
    void print() const { cout << "打印机状态:就绪" << endl; }   // 新加
};

六、常见误区

  1. "多重继承就是把单继承写两遍"——语法上是,语义上不是。"多个父类之间谁先构造""同名成员怎么办""有没有共同祖先"——这三个问题在单继承里根本不存在。
  2. "父类越多越好,能力越全"——不是。每多一个父类,就多一份耦合、多一处二义性风险。工程惯例:优先"组合"(把对象当成员装进来),只在"确实同时是两种东西"时才多重继承。
  3. "多重继承是我自己的事,不会影响别人"——会。菱形继承让你多一份祖先数据,别人传你这个对象时,参数 sizeof、指针调整都会受影响(11.3.2、11.4.3)。
  4. "继承顺序随便写"——顺序是有语义的:构造顺序就按这个顺序(11.3.3),析构正好反过来。在两边顺序不一致的设计里,这个顺序往往是 bug 的来源。
  5. "多重继承里忘了写 public 也没事"——会出事。class C : public A, B 里 B 默认 private 继承,B 的公有成员在 C 里就都变私有、外界访问不到(11.1.2 的表)。排查这个错经常要花很久,因为代码看起来"明明写了继承"。
自测三问(点开对答案) 1. 多重继承的语法是什么?最容易漏写什么?——`class 子类 : public A, public B { };`。**最容易漏写第二个父类前面的 `public`**(漏了就是 `private` 继承,父类公有成员在子类里全变私有)。 2. 多重继承带来的两个"独有麻烦"是什么?——**① 二义性**(两个父类有同名成员,`obj.x` 编译器不知道选哪个);**② 菱形继承**(两条路径通往同一个祖先,祖先的数据在对象里有**两份**)。 3. 决定"多个父类谁先构造"的是什么?——**继承声明里的书写顺序**(从左到右),**不是初始化列表里的顺序**(11.3.3 用实例验证)。

相关笔记