C++ 继承:面向对象的代码复用核心机制

C++ 继承:面向对象的代码复用核心机制

继承是面向对象三大特性(封装、继承、多态)中最能体现“代码复用”思想的机制。它允许我们从已有类(基类/父类)派生出新类(派生类/子类),从而直接获得基类的成员(数据 + 函数),并可按需扩展或修改行为。

1. 继承的基本语法与三种继承方式

// 基本写法
class 派生类名 : [继承方式] 基类名
{
    // 新增成员
};

C++ 支持三种继承方式,访问控制规则差异很大:

继承方式基类中 public 成员基类中 protected 成员基类中 private 成员最常用场景
publicpublicprotected不可访问最常用(接口继承、is-a 关系)
protectedprotectedprotected不可访问很少用(家族内部共享)
privateprivateprivate不可访问极少用(实现继承、has-a 伪装)

最推荐的记忆口诀(现代 C++ 几乎只用这一种):

99% 的情况下都写 public 继承
其他两种基本只在实现细节隐藏或框架设计时才会出现

2. 继承中最容易混淆的访问控制规则(面试高频)

class Base {
public:
    int    pub    = 1;
protected:
    int    prot   = 2;
private:
    int    priv   = 3;

public:
    void show() { std::cout << pub << prot << priv << "\n"; }
};

class Derived : public Base {
public:
    void test() {
        pub  = 10;   // OK
        prot = 20;   // OK
        // priv = 30; // 错误!private 成员不可见
    }
};

int main() {
    Derived d;
    d.pub  = 100;   // OK
    // d.prot = 200; // 错误!protected 对外不可见
    // d.priv = 300; // 错误
}

一句话总结访问规则

  • 基类的 private 成员永远只对基类自己可见(派生类也看不到)
  • 基类的 protected 成员对派生类可见,但对外隐藏
  • 继承方式决定“基类 public/protected 成员在派生类中的最低访问级别”

3. 继承中的构造函数与析构函数调用顺序(最容易出错)

规则(必须背下来):

  1. 构造顺序:先构造基类 → 再构造派生类
  2. 析构顺序:先析构派生类 → 再析构基类(与构造顺序相反)
class Base {
public:
    Base()          { std::cout << "Base 构造\n"; }
    ~Base()         { std::cout << "Base 析构\n"; }
};

class Derived : public Base {
public:
    Derived()       { std::cout << "Derived 构造\n"; }
    ~Derived()      { std::cout << "Derived 析构\n"; }
};

int main() {
    Derived d;
    // 输出顺序:
    // Base 构造
    // Derived 构造
    // Derived 析构
    // Base 析构
}

最重要结论

基类的析构函数必须是虚函数(几乎是铁律)

class Base {
public:
    virtual ~Base() = default;   // 强烈推荐写虚析构
};

如果基类析构不是虚函数,通过基类指针 delete 派生类对象时,只会调用基类析构函数 → 派生类部分资源泄漏。

4. 继承方式对比表(面试常考)

问题public 继承protected 继承private 继承
“is-a” 关系是否成立
能否通过基类指针/引用指向派生类对象否(编译错误)否(编译错误)
派生类能否向上转型
主要使用场景接口继承、实现继承内部实现细节共享实现细节完全隐藏(罕见)
2025–2026 实际使用比例≈95%≈4%<1%

5. 现代 C++ 中的继承使用建议(强烈推荐)

原则推荐做法理由 / 现代趋势
优先组合而非继承has-a > is-a组合更灵活,耦合更低
public 继承表示 is-a 关系只在真正满足里氏替换原则时使用避免违反 LSP(里氏替换原则)
基类析构函数一律写成 virtualvirtual ~Base() = default;防止通过基类指针 delete 导致内存泄漏
能用接口(纯虚类)就别用带数据成员的基类abstract base class(只有纯虚函数)接口隔离原则,更容易测试与替换
能用模板/CRTP 就尽量少用虚函数静态多态替代动态多态编译期决议,性能更高、可内联
禁用拷贝构造/赋值(如果不需要)=deleteRule of Zero / Rule of Five

6. 经典面试/笔试题型(建议都手写一遍)

  1. 写出下面代码的输出顺序(构造/析构顺序)
  2. 解释为什么基类析构函数要写成虚函数,并举例说明不写会发生什么
  3. 给出三种继承方式下,基类成员在派生类中的访问权限对比表
  4. 实现一个简单的 RAII 类(带虚析构)
  5. 解释“菱形继承”问题及 virtual 继承的解决方案(了解即可)

7. 现代 C++ 继承小结口诀

能组合不用继承
能接口不用数据成员
能虚函数用 CRTP 代替
基类析构永远写 virtual
public 继承写到手酸,其余两种基本不碰

你目前在学习/使用继承时最困惑的是哪一部分?

  • 虚继承与菱形问题?
  • 构造函数初始化列表的写法?
  • 多重继承的成员名冲突?
  • 纯虚函数与抽象类设计?
  • 继承与模板的结合(CRTP)?

可以直接告诉我,我会针对性给出更详细的代码示例与解释。

文章已创建 4893

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部