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

语法是 class 子类 : public A, public B { };——一个冒号、一列父类、每个前面都写 public。对象里是这样排的:A 那部分 + B 那部分 + 自己新增的(按继承声明顺序),所以 sizeof 大致是"各父类加起来"。访问不同父类的同名成员时要明确指名:obj.A::who()。

📝 本节和前面几节的关系

上一节(11.3)讲了"多重继承是什么、有什么麻烦";本节把语法和对象布局落定,方式和 11.1.1 落定单继承完全一致。这里能看到一个单继承里看不到的现象:"父类子对象"不止一个,而且各自有各的偏移——这正是 11.3.2 二义性的物理原因,也是 11.4.3 虚继承要动手改的东西。

一、语法:一列父类,每个都带 public

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

和单继承对照:

class A : public Base { };                     // 单继承:一个父类
class B : public P1, public P2, public P3 { }; // 多重继承:一列

四条规则:

规则 说明
逗号分隔 父类之间用 ,
每个父类前写继承方式 漏了就是 private(class 的默认)。这是多重继承里最常见的低级错
同一个直接父类不能写两次 class C : public A, public A ❌(重复继承同一个类是错的;菱形继承不是"重复写",而是"两条路径"——那是另一回事,11.3.2)
父类要有定义 至少要有完整定义(不像前向声明只能用于指针)

二、对象里怎么排:A 部分 + B 部分 + 自己

class A { public: int a; };
class B { public: int b; };
class C : public A, public B { public: int c; };

C 对象的内存:

┌──────────── C 对象 ────────────┐
│ ┌── A 子对象 ──┐               │   ← 按继承声明顺序,A 在前
│ │    a         │               │
│ └──────────────┘               │
│ ┌── B 子对象 ──┐               │
│ │    b         │               │
│ └──────────────┘               │
│ ┌── C 自己 ────┐               │
│ │    c         │               │
│ └──────────────┘               │
└────────────────────────────────┘

由此推出:

  1. sizeof(C) = 各父类子对象 + 自己新增的(4 + 4 + 4 = 12),可能因对齐再加填充。
  2. 每个父类子对象按"继承声明顺序"排——换一下书写顺序,对象布局就变(sizeof 可能还一样,但构造顺序会变,11.3.3 验证)。
  3. C* 转成 A* 时地址可以不变;转成 B* 时地址要往后挪(因为 B 子对象不在开头)——这种"指针调整"是多重继承的真实开销,也是为什么多继承的对象往往比"各部分之和"再大一点点(有些实现要存偏移信息)。

三、访问不同父类的同名成员:必须指名

class A { public: void who() { cout << "A"; } };
class B { public: void who() { cout << "B"; } };
class C : public A, public B { };

C obj;
// obj.who();          // ❌ 二义性:A 的 who 还是 B 的 who?
obj.A::who();          // ✅ 明确指名
obj.B::who();          // ✅

obj.A::who() 读作:"在 obj 这个对象里,找 A 那一部分,调它的 who"。

要区分两个概念(这也是 10.1.2 的 ::):

写法 含义
obj.A::who() 指名"从哪个父类那一份"调用(解决二义性)
C::who() 在类外定义 C 自己的 who 时标归属(10.1.2 的老用法)

四、动手 Demo:两个父类、三种访问

#include <iostream>
using namespace std;

class A {
public:
    int  a;
    A() : a(1) { }
    void who() const { cout << "我是 A" << endl; }
};

class B {
public:
    int  b;
    B() : b(2) { }
    void who() const { cout << "我是 B" << endl; }
};

// ★ 两个父类,各自都写 public
class C : public A, public B {
public:
    int c;
    C() : c(30) {                // A、B 会按声明顺序自动先构造
        a = 10;                  // a 是 public 的,可以在函数体里改
        b = 20;
    }
};

int main() {
    C obj;

    cout << obj.a << " " << obj.b << " " << obj.c << endl;

    // obj.who();                // ❌ 报二义性(A、B 都有 who)
    obj.A::who();                // ✅ 指名调用
    obj.B::who();                // ✅

    cout << "sizeof(A) = " << sizeof(A)
         << ",sizeof(B) = " << sizeof(B)
         << ",sizeof(C) = " << sizeof(C) << endl;
    return 0;
}

看到什么算成功:

10 20 30
我是 A
我是 B
sizeof(A) = 4,sizeof(B) = 4,sizeof(C) = 12

读结果:

现象 说明
obj.a、obj.b、obj.c 都能直接访问 两个父类的 public 成员都成了 C 对象的成员(没有重名,就没有二义)
obj.who() 编译不过,obj.A::who() 可以 同名成员必须指名——这就是 11.3.2 要展开的二义性
sizeof(C) = 12 = 4 + 4 + 4 对象里同时存在 A 和 B 两个子对象,再加上自己那个 c

30 秒实验: 1. 把 class C : public A, public B 改成 class C : public A, B(把 B 前面的 public 删掉)。 2. 重新编译 → obj.b 报错:'int B::b' is inaccessible(或 is private within this context)。 3. 原因:B 变成了 private 继承,于是 B 的 public 成员在 C 里被降级成 private——外界(main)就碰不到了(11.1.2 的表)。 4. 加回 public,就好。 多重继承里,这个错会以"父类名 ×2"的形式出现,务必检查每一个父类前面的关键字。

五、常见误区

  1. "多个父类之间用分号或空格分隔"——用逗号 ,。写成分号是语法错。
  2. "第二个父类前的继承方式可以省"——不能省,省了就是 private 继承,父类公有成员在子类里统统变私有。这是多重继承里最典型的"代码看起来对、实际访问不到"的 bug。
  3. "对象里两个父类的部分混在一起"——不是。它们是两个独立的子对象,按继承声明顺序排列。这也是为什么 sizeof(C) 是各个部分加起来(要留一份 A 的、一份 B 的)。
  4. "C* 转 A* 和转 B* 一样简单"——不一样。只有一个父类子对象在对象开头,另一个不在。"转到不在开头的那个"需要做指针调整,这也是多重继承带来的额外开销。
  5. "多重继承中的 obj.who() 会自动挑一个"——不会,直接报错。C++ 的原则是"有歧义就让编译器报错,绝不替你猜"——报错比猜错好。
自测三问(点开对答案) 1. 多重继承的语法怎么写?最容易漏什么?——`class 子类 : public A, public B { };`。**最容易漏第二个及之后父类前的 `public`**(漏了就是 `private` 继承)。 2. 一个 `C : public A, public B` 的对象里,内存怎么排?——**A 的子对象 + B 的子对象 + C 自己新增的**,顺序按继承声明顺序;`sizeof(C)` 大致是各部分之和(可能加对齐填充)。 3. `obj.who()` 报二义性时怎么解决?——**明确指名**:`obj.A::who();` 或 `obj.B::who();`。**(另一种思路是在 `C` 里自己定义一个 `who()`,把那两个名字都盖住——但通常不如指名清楚。)**

相关笔记