继承:类与类的层级

is-a 关系,以及菱形继承怎么绕开

商用

◎学完你会

1继承层级 & 菱形继承对比

左:点层级中的每一类看它是什么关系。右:菱形继承里 A 会被复制几份?点按钮对比。

Animal(动物)
▲ is-a
Dog(狗)
▲ is-a
Hound(猎犬)
点左边任一层。
D ──继承──▶ B(B ──继承──▶ A)
D ──继承──▶ C(C ──继承──▶ 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 { … }; 表示 Dog 和 Animal 是什么关系?
公有继承表达 is-a:Dog 是 Animal 的子类,能当 Animal 用。
D 继承 B 和 C,B、C 都继承 A(菱形继承),默认情况下 D 里 A 有几份?
默认 B、C 各带一份 A → D 里有 两份 A,访问基类成员会二义性。
要让菱形继承里 A 只保留一份、消除二义性,应该怎么做?
虚继承(virtual)让 A 只保留一份,B、C 共享它,D::x 不再二义。