14. 继承与派生:单继承、多继承、虚继承、继承中的构造函数与析构函数、访问控制

继承,说白了就是代码复用的一种高级手段。你写了一个类,另一个类想直接拿它的功能,再往上加点自己的东西——这就是继承。我刚开始学C++那会儿,觉得继承不就是“儿子拿老子的东西”嘛,后来踩了不少坑才明白,这里面的门道远比想象的多。

核心一句话:继承是C++面向对象的三大特性之一,它允许我们基于已有类创建新类,复用、扩展、甚至改写原有行为。

14.1 单继承:最朴素的复用方式

单继承就是一个派生类只从一个基类继承。这是最常用的形式,也是我建议初学者最先掌握的。

class Animal {
public:
    void eat() { cout << "吃东西" << endl; }
};

class Dog : public Animal {
public:
    void bark() { cout << "汪汪" << endl; }
};

你看,Dog 类没写 eat 方法,但它能用。这就是继承的直接好处。我在项目中遇到过很多新手,恨不得把所有功能塞到一个类里,其实拆开用继承,代码清爽得多。

14.2 多继承:一把双刃剑

C++ 允许一个类继承多个基类。听起来很强大,对吧?但我要说句实话:多继承用不好,就是给自己挖坑。

class Teacher {
public:
    void teach() { cout << "教书" << endl; }
};

class Researcher {
public:
    void research() { cout << "科研" << endl; }
};

class Professor : public Teacher, public Researcher {
public:
    void guide() { cout << "指导研究生" << endl; }
};

这个例子还算简单。但如果你两个基类都有同名方法,或者有相同的基类,问题就来了。嗯,这里要注意——多继承最大的坑就是“菱形继承”。

我曾经踩过的坑:两个基类都继承自同一个父类,派生类再同时继承这两个基类,结果数据成员被复制了两份。调用父类方法时,编译器直接报歧义错误。那会儿排查了一下午,最后发现是继承链设计出了问题。

14.3 虚继承:解决菱形继承的钥匙

为什么会这样?因为默认情况下,每个继承路径都会创建一份基类的副本。虚继承就是告诉编译器:不管通过几条路径继承这个基类,只保留一份。

class Animal {
public:
    int age;
};

class Mammal : virtual public Animal {};
class Bird : virtual public Animal {};
class Bat : public Mammal, public Bird {};  // 只有一份 Animal

加了 virtual 关键字后,Bat 类里只有一份 age 成员。我个人习惯是:只要发现继承层次超过两层,就考虑用虚继承。宁可多写几个 virtual,也别等出 bug 再回头改。

小技巧:虚继承的构造函数调用规则比较特殊——最派生类要负责初始化虚基类。你想想看,如果让中间类去初始化,那到底听谁的?所以 C++ 规定:虚基类由最终派生类直接初始化。

14.4 继承中的构造函数与析构函数

这里有个顺序问题,我建议你死记硬背:构造从基类到派生类,析构从派生类到基类。

class Base {
public:
    Base() { cout << "Base构造" << endl; }
    ~Base() { cout << "Base析构" << endl; }
};

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

输出顺序是:Base构造 → Derived构造 → Derived析构 → Base析构。就像盖房子,先打地基再盖楼,拆的时候先拆楼再拆地基。

派生类构造函数怎么给基类传参?用初始化列表:

class Base {
public:
    Base(int x) { cout << "Base: " << x << endl; }
};

class Derived : public Base {
public:
    Derived(int a, int b) : Base(a) {
        cout << "Derived: " << b << endl;
    }
};

注意:如果基类没有默认构造函数,派生类必须在初始化列表中显式调用基类构造函数。否则编译直接报错,没得商量。

14.5 访问控制:谁能看到什么

继承方式有三种:public、protected、private。说白了就是控制基类成员在派生类中的访问权限。

基类成员权限 public继承 protected继承 private继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

我个人的经验是:绝大多数场景用 public 继承就够了。protected 继承偶尔用在框架设计中,private 继承我几乎不用——它更像是一种“实现复用”而不是“接口复用”。

避坑指南:我曾经用 private 继承想隐藏基类接口,结果派生类想调用基类方法还得用 using 声明重新暴露,代码越写越别扭。后来改用组合模式,清爽多了。所以我的建议是:能用组合就别用 private 继承。

知识体系总览

下面这张图是我整理的本章核心脉络,你看一眼就能把整个继承体系串起来:

继承与派生 单继承 一个派生类 一个基类 多继承 一个派生类 多个基类 虚继承 解决菱形继承 共享基类实例 构造与析构 构造:基类→派生类 析构:派生类→基类 访问控制:public / protected / private

这张图把本章的五个核心知识点串在了一起。你从上往下看:继承是总纲,下面分出单继承、多继承、虚继承、构造析构,最下面是访问控制贯穿始终。我个人觉得,把这五个点吃透了,C++ 的继承体系你就掌握了七八成。

嗯,最后说一句:继承不是万能的。我在实际项目中见过有人为了复用两行代码,硬生生搞出五层继承,最后改一个基类,所有派生类都跟着崩。该用组合的时候别犹豫,该用继承的时候也别怕。工具本身没有好坏,关键看你怎么用。


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