Skip to content

9 继承和派生

继承和派生的概念

继承(Inheritance)是面向对象编程的三大特性之一,它允许一个类(子类/派生类)基于另一个类(父类/基类/超类)来构建,从而复用父类的属性和方法。

派生(Derivation)是指从已有类创建新类的过程。

通过继承机制,子类可以:

  • 复用代码:继承父类的成员变量和成员函数
  • 扩展功能:添加新的属性和方法
  • 重写方法:对父类的方法进行重新实现(多态的基础)

继承关系

  • 单继承:一个子类只继承自一个父类
  • 多重继承:一个子类同时继承自多个父类
  • 重复继承(多重继承的一种特殊形式):派生类两次或两次以上重复继承某个祖先类(需要通过虚继承解决)
    A (祖先类)
   / \
  B   C (两个父类)
   \ /
    D (子类)
  • 直接继承:子类直接从父类继承
  • 间接继承:子类通过中间类继承自祖先类

语法与访问控制

单继承的语法:

class 子类名 : 继承访问控制 父类名 
{
    // 子类的成员变量和成员函数
}

成员访问控制

  • public:公共访问,子类可以访问父类的公共成员,可以被继承
  • protected:受保护访问,子类可以访问父类的受保护成员,可以被继承
  • private:私有访问,只能通过本类的成员函数来访问。不可被继承

继承访问控制

访问控制符 父类 public 成员 父类 protected 成员 父类 private 成员
public public(保持不变) protected(保持不变) 不可访问
protected protected protected 不可访问
private private private 不可访问

如果不写,默认类继承是 private,结构体默认 public。

构造与析构顺序

派生类对象的存储

派生类对象总是在前面存放基类中定义的非静态数据成员,后面存放派生类中定义的非静态数据成员。

即:派生类总是先进行基类部分的初始化,默认使用基类无参构造。

初始化

  • 基类的构造函数不被继承,派生类中需要声明自己的构造函数。
  • 派生类的构造函数中只需要对本类中新增成员进行初始化即可。
  • 对继承来的基类成员的初始化是通过编译在派生类构造函数初始化器中自动使用无参构造函数(或拷贝构造)完成。如果基类没有默认构造(包括 =delete),则编译错误。
    class Animal {
    public:
        Animal() {  // 父类默认构造函数
            cout << "Animal constructor" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        Dog() : Animal() {  // 也可以不写:Animal(),编译器会自动调用父类无参构造
            cout << "Dog constructor" << endl;
        }
    };
    

派生类的构造函数需要使用基类的有参构造函数,必须显式在初始化器列表中申明。不能在构造函数内调用!

class Animal {
public:
    Animal(string name) : m_name(name) {}
protected:
    string m_name;
};

class Dog : public Animal {
public:
    // 传递参数给父类构造函数
    Dog(string name) : Animal(name) {}  
};

构造函数的调用次序(创建派生类对象时):

  • 首先调用其基类的构造函数(多重继承:调用顺序按照基类被继承时的声明顺序(从左向右)class C : public A, public B)。
  • 然后调用本类对象成员的构造函数(调用顺序按照对象成员在类中的声明顺序)。
  • 最后调用本类的构造函数。