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

继承方式(public/protected/private)不改变父类成员自己的权限,它改变的是"这些成员进了子类之后,在子类里算什么级别"。核心就是一张 3×3 表:父类 public / protected 成员 → 按继承方式"取更严的那个"降级;父类 private 成员 → 无论哪种继承方式,子类都碰不到。判断标准永远是同一句话:"从子类里面看"和"从外界看",分别能不能访问到。

📝 本节和前面几节的关系

10.1.1 讲了类的三档权限(private / protected / public),那时 protected 是"悬空的"——没有任何东西能验证它的含义,因为还没有继承。本节把它落地:protected 的唯一意义就是"给子类留的门"。同时也回收 11.1.1 的坑:class D : B 默认是 private 继承,导致父类公开成员"突然访问不到"——本节给出原因(那张表)和解药。

一、先明确:问题不是"父类成员变了",而是"进了子类之后变了"

一个关键区分:

说法 对不对
"父类的 public 被改成了 protected" ❌ 父类那边什么都没变
"父类的 public 成员,在子类里(以及通过子类)算作 protected" ✅ 这样想才对

权限的"两把尺子":

  1. 从子类内部看:能不能在子类的成员函数里直接写 pub = 10;?
  2. 从外界看:外人能不能写 obj.pub = 10;(obj 是子类对象)?

这两种看法会得到不同答案——这是本节所有困惑的根源。

二、核心表(3×3,务必背下来)

设 Base 里有三种成员:public: pub; / protected: pro; / private: pri;

Base 中的权限 class D : public Base class D : protected Base class D : private Base
public → public protected private
protected → protected protected private
private → 不可访问 不可访问 不可访问

再补一列"外界视角"(用 D 的对象 d):

写法 public 继承 protected 继承 private 继承
d.pub ✅ 能 ❌ 不能(已是 protected) ❌ 不能(已是 private)
d.pro ❌ 不能 ❌ 不能 ❌ 不能
d.pri ❌ 不能(父类私有) ❌ 不能 ❌ 不能
子类内部写 pub ✅ 能 ✅ 能 ✅ 能
子类内部写 pro ✅ 能 ✅ 能 ✅ 能
子类内部写 pri ❌ 不能 ❌ 不能 ❌ 不能

看出规律没有:"子类内部能不能用 pub/pro" 三种继承方式完全一样(都能用)——继承方式只影响"外界能不能用"以及"子类的子类能不能用"。

三、两条记忆法

记忆法一:取小原则

把三档权限排成"严格程度":

public  <(更宽松)  protected  <(更严格)  private

派生后的权限 = 继承方式 与 原权限 中"更严"的那个:

一句话"越继承越封闭":继承方式只会让权限变严,不会变宽松。

记忆法二:private 是黑箱

父类的 private 成员,是"上锁的抽屉"。

这解释了为什么第十章反复强调"好类要留有意义的行为接口"——父类的 private 数据,子类只能通过这些接口用。接口设计得烂,子类就被卡死。

四、protected 的准确含义

protected 只对"自己的子孙"开门:

class Base {
protected:
    int pro;          // 谁能碰?Base 自己 + 所有继承 Base 的子孙
};

三个判断:

场景 能不能访问 pro
Base 自己的成员函数里 ✅ 能
子类 D 的成员函数里 ✅ 能
子类 D 的对象 d(外界) ❌ 不能
与 Base 毫无关系的类 X ❌ 不能

protected 的精确描述:"家族内部公开,对外保密"。它和 private 的差别只在子类这一层。

五、三种继承方式实际怎么用

继承方式 含义 什么时候用
public is-a:"子类是一种父类" 95% 的场合。父类的接口就是子类接口的一部分。本章后面(多态、抽象类)全部建立在 public 继承上
protected "我继承你,但你的接口只给我的子孙看" 很罕见。多数场合说明设计没想清楚
private has-a(实现复用):"我借用你的实现,但我不是你的子类" 少见。现代 C++ 更推荐写成成员变量("组合优于继承")

工程上的常识:"组合优于继承"。如果只是"想用父类的代码"(不是"是一种"),把那个类做成一个成员变量更安全——耦合更松,也不会有本节这张表的负担。

六、能不能改变继承了之后的权限

可以,用 using 声明(现代 C++ 的写法):

class Base {
protected:
    void f();
public:
    void g();
};

class D : private Base {            // 继承后:f、g 在 D 里都是 private
public:
    using Base::f;                  // ★ 把 f 的权限"提"回 public
    using Base::g;                  // ★ 把 g 也提回 public
};

注意边界:

这属于"知道有这回事"级别,教材里的重点还是那张表。

七、动手 Demo:一张表全部验证一遍

#include <iostream>
using namespace std;

class Base {
public:
    int pub;
protected:
    int pro;
private:
    int pri;                        // 子类永远碰不到

public:
    Base() : pub(1), pro(2), pri(3) {}
    int getPri() const { return pri; }        // 唯一通道
};

class PubDerived : public Base {
public:
    void test() {
        pub = 10;                   // ✅ 可以
        pro = 20;                   // ✅ 可以(protected 是"家族内部"的)
        // pri = 30;                // ❌ 编译错:'pri' is private within this context
        cout << "子类内部:pub=" << pub
             << " pro=" << pro
             << " pri(通过通道)=" << getPri() << endl;
    }
};

class ProDerived : protected Base {
public:
    void test() {
        pub = 100;                  // ✅ 子类内部照样能改
        cout << "protected 继承,子类内部仍能改 pub=" << pub << endl;
    }
};

int main() {
    PubDerived d;
    d.pub = 1000;                   // ✅ public 继承:外界能碰
    // d.pro = 2000;                // ❌ protected 出不了家族
    // d.pri = 3000;                // ❌ 父类私有,子类都碰不到
    d.test();
    cout << "外界:pub=" << d.pub << endl;

    cout << "── 换成 protected 继承 ──" << endl;
    ProDerived e;
    // e.pub = 1;                   // ❌ 已经是 protected 了,外界碰不到
    e.test();                       // 但子类自己还能用
    return 0;
}

看到什么算成功:

子类内部:pub=10 pro=20 pri(通过通道)=3
外界:pub=1000
── 换成 protected 继承 ──
protected 继承,子类内部仍能改 pub=100

读结果(这张表的三行验证):

结论 由哪一行验证
父类 private → 子类碰不到(注释掉的那行注释掉是对的) pri(通过通道)=3 只能靠 getPri() 读出来
父类 public + public 继承 → 外界能碰 d.pub = 1000; 编得过
继承方式只影响外界,子类内部照旧 ProDerived 里 pub = 100; 依然编得过,而 e.pub = 1; 编不过

30 秒实验: 1. 把 PubDerived 的 : public Base 改成 : protected Base,重新编译。 2. 你会看到 d.pub = 1000; 报错:'int Base::pub' is protected within this context(有的编译器写 is inaccessible)。 3. 而 test() 里的 pub = 10; 依然没问题——这就是"继承方式只影响外界、不影响子类内部"的铁证。 4. 再把注释掉的 pri = 30; 恢复,看看父类 private 成员在子类里到底报什么错脸。

再试一次反向的:把父类的 protected: 改成 private:,pro = 20; 立刻报 'pro' is private within this context。两处对照,protected 的意义就锁死了。

八、常见误区

  1. "private 继承 = 继承不到父类的成员"——错。父类的 public / protected 成员在子类里都还在,子类内部照样随便用,只是它们对外(包括对子类的子类)都变成了 private。继承方式管的是"对外显示成什么级别",不是"能不能拿到"。
  2. "父类的 private 成员被我改权限就能用了"——不能。private 是"上锁的抽屉",任何继承方式、任何 using 声明都打不开。这是父类设计者明确表态的边界,必须尊重。
  3. "子类对象能访问自己的 protected 成员"——边界很微妙:在子类自己的成员函数里可以(this->pro = 5;),但通过"基类类型"的引用/指针操作别的对象时不行。别在这上面纠缠,记住实践规则:protected 成员只在"我自己的成员函数里"用。
  4. "protected 比 private 好,因为更灵活"——不是。protected 是"对子类的公开承诺",改了会波及所有子孙。能 private 就 private,确实需要给子类用才放 protected。
  5. "三种继承方式随便选,反正我不写就能编过"——能编过 ≠ 正确。class D : B(默认 private)编得过,但语义上完全变了:从"是一种"变成了"偷偷借来用"。每次写继承,第一件事就是把 public 写上去。
自测三问(点开对答案) 1. 父类的 `public` 成员在 `private` 继承下,在子类里算什么权限?外界还能访问吗?——在子类里算 **`private`**(取更严的那个)。**外界不能访问**;**子类自己的成员函数能访问**;**子类的子类不能访问**。 2. 父类的 `private` 成员,用哪种继承方式子类能直接访问?——**哪种都不能**。它"存在但上锁",只能通过父类提供的 `public` / `protected` 函数间接使用。 3. `protected` 继承和 `private` 继承的差别是什么(只要一个字的差别)?——差别在**子类的子类**:`protected` 继承时,父类的 `public` 成员在子类的子类里仍是 `protected`(**还能传下去**);`private` 继承时变成 `private`(**到此为止,再也传不下去**)。

相关笔记