一、继承模型
1. 单继承
一个派生类只有一个直接基类,就是单继承。
2. 多继承
一个派生类有两个及以上直接基类,就是多继承。 内存布局:先继承的基类在前面,后继承的基类在后面,派生类自身成员放在最后。
3. 菱形继承(钻石继承)
菱形继承是多继承的特殊情况。
-
问题 :普通多继承会造成数据冗余、二义性,公共基类成员存多份。
-
解决:虚继承
virtual:虚继承之后公共基类只保留一份。
Java 直接不支持多继承,直接规避菱形继承问题,工程实践尽量避免设计菱形继承。
二、多继承指针偏移问题
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
-
p1指向对象起始地址,对应Base1子对象; -
p2指向Base2子对象,指针会向后偏移; -
p3指向完整派生类对象起始地址。
所以
p1 != p2,p1 == p3。多继承向上转型,不同基类指针地址不一定相等。
三、继承和组合
本质:都是复用成员。
-
public 继承:is‑a 派生类对象本质就是一个基类对象,属于白箱复用。 子类可以访问父类保护成员,耦合度高,父类改动会强烈影响子类。
-
组合:has‑a 把类对象作为另一个类的成员,属于黑箱复用。 内部细节对外不可见,类之间依赖弱,耦合度低。
✅工程原则:优先使用组合,而不是继承
-
is‑a 关系,用继承;
-
has‑a 关系,优先组合;
-
需要实现多态,必须使用继承。
高内聚、低耦合是软件工程核心追求。
四、多态
1. 多态两个必要条件
-
基类指针 / 引用调用函数;
-
调用的是虚函数,派生类完成虚函数重写 (覆盖)。
2. 虚函数重写(三同)
派生类虚函数和基类虚函数,返回值、函数名、参数列表全部相同,叫重写。
小坑:派生类不加 virtual,只要满足三同,依然构成重写,语法允许,但可读性差,考试选择题高频坑点。
3. 虚析构函数
基类析构建议写为virtual虚析构。
-
如果基类析构是虚函数,派生类析构自动构成重写,不管写不写 virtual。
-
delete 基类指针指向派生类对象,只有基类析构为虚,才会调用派生类析构,否则只会调用基类析构,派生类资源泄漏。
五、重载、重写 (覆盖)、隐藏三大概念对比
-
重载:同一个作用域,函数名相同,参数列表不同。
-
重写 / 覆盖:继承体系,基类和派生类,虚函数,三同。
-
隐藏:派生类定义和基类同名函数,不管参数是否相同,基类同名函数直接被隐藏。
六、纯虚函数 & 抽象类
virtual void func() = 0;
-
虚函数后面写
=0,就是纯虚函数,可以不写函数体。 -
拥有纯虚函数的类叫抽象类。
-
抽象类不能实例化对象。
-
派生类必须重写全部纯虚函数,否则派生类也变成抽象类,也不能实例化。
抽象类强制约束派生类必须实现接口。
考点小结
-
菱形继承:普通继承造成二义性冗余,
virtual虚继承解决; -
多继承向上转型,不同基类指针地址会偏移,不一定相等;
-
优先组合,is‑a 才用继承;
-
多态两大条件:基类指针 / 引用 + 虚函数重写;
-
基类析构建议 virtual,防止派生类内存泄漏;
-
抽象类包含纯虚函数,不能实例对象,强制子类实现接口。