25. 继承:单继承、继承方式(public/protected/private)、构造析构顺序

继承,说白了就是“儿子继承老子的家业”。在 C++ 里,一个类可以继承另一个类的成员(数据和函数),从而复用代码、建立层次关系。今天咱们就聊聊单继承,以及三种继承方式到底有什么区别,还有构造和析构的顺序问题。

我个人习惯把继承看作一种“is-a”关系。比如“狗”继承“动物”,那么狗就是一种动物。这种关系在现实世界中很自然,在代码里也一样。

单继承的基本语法

单继承就是一个派生类只从一个基类继承。语法很简单:

class Base {
    // 基类成员
};

class Derived : public Base {
    // 派生类成员
};

这里的 public 就是继承方式。除了 public,还有 protectedprivate。它们决定了基类成员在派生类中的访问权限。

三种继承方式

我刚开始学的时候,总觉得这三种方式容易搞混。后来我总结了一个规律:继承方式决定了基类成员在派生类中的“最低权限”

继承方式 基类 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 继承。
  • 基类一定要有虚析构函数,除非你确定不会通过基类指针删除派生类对象。

知识体系图

下面这张图展示了单继承的核心知识点和它们之间的关系:

单继承知识体系 单继承 继承方式 public: is-a 关系 protected: 子类可访问 private: 实现复用 构造析构顺序 构造: 基类 → 派生类 析构: 派生类 → 基类 虚析构函数很重要 注意事项 避免菱形继承 优先用组合 不要过深继承 核心:继承是代码复用,但不要滥用

这张图把单继承的三个核心方面串起来了。继承方式决定了访问权限,构造析构顺序决定了生命周期,而注意事项则是实战中容易翻车的地方。

最后说一句:继承是 C++ 面向对象编程的基石,但也是最容易被滥用的特性之一。我见过太多人为了“复用”而搞出七八层继承,最后改一个基类,整个项目都要重新编译。嗯,适度使用,才是正道。


公众号:蓝海资料掘金营,微信deep3321