继承方式(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" |
✅ 这样想才对 |
权限的"两把尺子":
- 从子类内部看:能不能在子类的成员函数里直接写
pub = 10;? - 从外界看:外人能不能写
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
派生后的权限 = 继承方式 与 原权限 中"更严"的那个:
public继承 ×public成员 → 取更严的 → 还是publicpublic继承 ×protected成员 → 取更严的 →protectedprotected继承 ×public成员 → 取更严的 →protectedprivate继承 × 任何成员 → 更严的永远是private
一句话"越继承越封闭":继承方式只会让权限变严,不会变宽松。
记忆法二:private 是黑箱
父类的 private 成员,是"上锁的抽屉"。
- 子类继承了这把锁着的抽屉(内存里有,也被父类正常初始化)。
- 子类打不开它——三列全是"不可访问"。
- 外界更打不开。
- 唯一的开锁方式:父类自己提供的
public/protected函数(getter、setter、业务方法)。
这解释了为什么第十章反复强调"好类要留有意义的行为接口"——父类的 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
};
注意边界:
using只能把子类里"能访问到"的成员提上来(即父类的public/protected)。父类private成员连名字都访问不到,提不了。using可以提权,不能降权(把public成员在子类里降成private,要另外想办法——这不是好设计,别做)。
这属于"知道有这回事"级别,教材里的重点还是那张表。
七、动手 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 的意义就锁死了。
八、常见误区
- "
private继承 = 继承不到父类的成员"——错。父类的public/protected成员在子类里都还在,子类内部照样随便用,只是它们对外(包括对子类的子类)都变成了private。继承方式管的是"对外显示成什么级别",不是"能不能拿到"。 - "父类的
private成员被我改权限就能用了"——不能。private是"上锁的抽屉",任何继承方式、任何using声明都打不开。这是父类设计者明确表态的边界,必须尊重。 - "子类对象能访问自己的
protected成员"——边界很微妙:在子类自己的成员函数里可以(this->pro = 5;),但通过"基类类型"的引用/指针操作别的对象时不行。别在这上面纠缠,记住实践规则:protected成员只在"我自己的成员函数里"用。 - "
protected比private好,因为更灵活"——不是。protected是"对子类的公开承诺",改了会波及所有子孙。能private就private,确实需要给子类用才放protected。 - "三种继承方式随便选,反正我不写就能编过"——能编过 ≠ 正确。
class D : B(默认private)编得过,但语义上完全变了:从"是一种"变成了"偷偷借来用"。每次写继承,第一件事就是把public写上去。
自测三问(点开对答案)
1. 父类的 `public` 成员在 `private` 继承下,在子类里算什么权限?外界还能访问吗?——在子类里算 **`private`**(取更严的那个)。**外界不能访问**;**子类自己的成员函数能访问**;**子类的子类不能访问**。 2. 父类的 `private` 成员,用哪种继承方式子类能直接访问?——**哪种都不能**。它"存在但上锁",只能通过父类提供的 `public` / `protected` 函数间接使用。 3. `protected` 继承和 `private` 继承的差别是什么(只要一个字的差别)?——差别在**子类的子类**:`protected` 继承时,父类的 `public` 成员在子类的子类里仍是 `protected`(**还能传下去**);`private` 继承时变成 `private`(**到此为止,再也传不下去**)。- ☐ 11.1.3 构造函数的调用顺序(下一节)