25. 继承:单继承、继承方式(public/protected/private)、构造析构顺序
继承,说白了就是“儿子继承老子的家业”。在 C++ 里,一个类可以继承另一个类的成员(数据和函数),从而复用代码、建立层次关系。今天咱们就聊聊单继承,以及三种继承方式到底有什么区别,还有构造和析构的顺序问题。
我个人习惯把继承看作一种“is-a”关系。比如“狗”继承“动物”,那么狗就是一种动物。这种关系在现实世界中很自然,在代码里也一样。
单继承的基本语法
单继承就是一个派生类只从一个基类继承。语法很简单:
class Base {
// 基类成员
};
class Derived : public Base {
// 派生类成员
};
这里的 public 就是继承方式。除了 public,还有 protected 和 private。它们决定了基类成员在派生类中的访问权限。
三种继承方式
我刚开始学的时候,总觉得这三种方式容易搞混。后来我总结了一个规律:继承方式决定了基类成员在派生类中的“最低权限”。
| 继承方式 | 基类 public 成员 | 基类 protected 成员 | 基类 private 成员 |
|---|---|---|---|
| public 继承 | public | protected | 不可访问 |
| protected 继承 | protected | protected | 不可访问 |
| private 继承 | private | private | 不可访问 |
你看,基类的 private 成员无论如何都访问不到。这是 C++ 的设计原则:私有成员只属于自己,连儿子都不能碰。
重点:实际开发中,90% 以上的情况都用 public 继承。因为只有 public 继承才能表达“is-a”关系。protected 和 private 继承更多用于实现细节复用,但容易让代码变得难以理解。
构造与析构的顺序
嗯,这里要注意。派生类对象创建时,构造函数的调用顺序是:先基类,后派生类。析构时正好相反:先派生类,后基类。
为什么会这样?你想想看,派生类是在基类的基础上“加东西”。所以构造时得先把地基打好(基类),再往上盖房子(派生类)。析构时得先把房子拆了(派生类),再拆地基(基类)。
看个例子:
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base 构造\n"; }
~Base() { cout << "Base 析构\n"; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived 构造\n"; }
~Derived() { cout << "Derived 析构\n"; }
};
int main() {
Derived d;
return 0;
}
输出结果:
Base 构造
Derived 构造
Derived 析构
Base 析构
我在项目中遇到过一个问题:某个派生类的构造函数里用到了基类的成员变量,结果因为基类还没构造完,导致访问了未初始化的数据。嗯,从那以后我写代码都会特别注意构造顺序。
避坑指南
我曾经踩过的坑:
- 在派生类构造函数中调用基类的虚函数——基类还没完全构造好,虚函数可能不会按预期调用。
- 忘记给基类写虚析构函数——如果通过基类指针删除派生类对象,会导致派生类析构函数不被调用,造成资源泄漏。
- 滥用 private 继承——除非你真的需要“实现为”关系,否则用 public 继承更清晰。
我的建议:
- 能用组合就别用继承。继承会带来强耦合,组合更灵活。
- 如果必须用继承,优先考虑 public 继承。
- 基类一定要有虚析构函数,除非你确定不会通过基类指针删除派生类对象。
知识体系图
下面这张图展示了单继承的核心知识点和它们之间的关系:
这张图把单继承的三个核心方面串起来了。继承方式决定了访问权限,构造析构顺序决定了生命周期,而注意事项则是实战中容易翻车的地方。
最后说一句:继承是 C++ 面向对象编程的基石,但也是最容易被滥用的特性之一。我见过太多人为了“复用”而搞出七八层继承,最后改一个基类,整个项目都要重新编译。嗯,适度使用,才是正道。
公众号:蓝海资料掘金营,微信deep3321