继承:类与类的层级
is-a 关系,以及菱形继承怎么绕开
商用
◎ 学完你会
说清继承的 is-a 关系与三种继承方式(public/private/protected);
知道构造顺序"先基类后派生类"、析构相反;
理解菱形继承 的"A 被复制两份"问题,以及用虚继承 解决。
1 继承层级 & 菱形继承对比
左:点层级中的每一类看它是什么关系。右:菱形继承里 A 会被复制几份?点按钮对比。
Animal(动物)
▲ is-a
Dog(狗)
▲ is-a
Hound(猎犬)
点左边任一层。
D ──继承──▶ B(B ──继承──▶ A) D ──继承──▶ C(C ──继承──▶ A)默认:A 被复制 两份
D 虚继承 B、C(B/C 都 virtual 继承 A)虚继承:A 只保留 一份
默认继承(A 两份)
虚继承(A 一份)
2 关键命令
写法 含义
class Dog : public Animal { … };公有继承:Dog 是 Animal 的一种(is-a)
Animal::name显式访问基类中被派生类隐藏的同名成员
class D : public B, public C多继承:D 同时是 B 和 C 的子类
class B : virtual public A虚继承:菱形时 A 只保留一份
✅ 继承权限:public 保留原访问级别(最常用);private/protected 会降级 基类成员的可见性,仅在"实现复用"时用。
3 构造与析构顺序:先基类,后派生
构造派生类对象时,先构造基类部分,再构造派生类自己的成员 ;析构时相反 (先派生后基类)。
class Base { public : Base(){ cout << "Base 构造" ; } };
class Der : public Base {
public : Der(){ cout << "Der 构造" ; }
~Der(){ cout << "Der 析构" ; }
};
Der d; // 输出顺序:Base 构造 → Der 构造 → Der 析构 → Base 析构
为什么必须先基类: 派生类可能用到基类已初始化的成员(如基类的 protected 数据),所以基类要先建好。析构反过来,先拆自己的,再拆地基。
4 菱形继承:A 被复制两份怎么办
B、C 都继承 A,D 又同时继承 B 和 C——如果不处理,D 里会有 两份 A (一份来自 B、一份来自 C)。
class A { public : int x; };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
// virtual 继承后:D 里 A 只有一份,D.x 不再有二义性
不虚继承的后果: D 里有两份 A,访问 D::x 产生二义性 (不知道是哪份 A 的 x),编译器报错。用 virtual 继承让 A 只保留一份,消除歧义。
! 易错点
① 混淆继承与组合 ——Dog 是 Animal 用继承(is-a);车拥有 发动机是组合(has-a),别用继承。
② 构造顺序记反 ——总是先基类后派生 ;析构相反。
③ 菱形继承忘虚继承 ——A 被复制两份 → 访问基类成员二义性。
④ 派生类隐藏基类同名函数后找不到 ——用 Base::fun() 显式访问。
? 跨学科:继承,就像"分类学的层级"
生物分类里"猎犬是狗,狗是动物"——猎犬自动继承了动物的所有特征,再叠加自己的特点。这就是 is-a:子类是父类的一种,能当父类用 。
菱形继承像"两门学科都归属理科,交叉学科又同时属于这两门"——若两门都各自带了理科的整套属性,交叉学科就得解释"我到底继承哪份"。虚继承就是"理科只算一份,两门共享它"。
✎ 练一练
★ 入门 · class Dog : public Animal 表示? ▶
class Dog : public Animal { … }; 表示 Dog 和 Animal 是什么关系?
A. 组合(has-a,Dog 拥有 Animal)
B. 继承(is-a,Dog 是 Animal 的一种)
C. 包含关系
D. 友元关系
公有继承表达 is-a :Dog 是 Animal 的子类,能当 Animal 用。
★★ 巩固 · 菱形继承默认会有几份 A? ▶
D 继承 B 和 C,B、C 都继承 A(菱形继承),默认情况下 D 里 A 有几份?
A. 一份
B. 两份(一份来自 B、一份来自 C)
C. 零份
D. 编译直接报错
默认 B、C 各带一份 A → D 里有 两份 A ,访问基类成员会二义性。
★★★ 挑战 · 怎么让菱形只保留一份 A? ▶
要让菱形继承里 A 只保留一份、消除二义性,应该怎么做?
A. 改用 private 继承
B. 让 B、C 都 virtual public A(虚继承)
C. 让 D 不要继承 B 和 C
D. 把 A 改成友元
虚继承 (virtual)让 A 只保留一份,B、C 共享它,D::x 不再二义。
✅ 我学懂了