程序设计实习(3) —— 继承与派生

基本概念

在定义一个新的类 B 时, 如果 B 类与拥有已有类 A 的全部特点, 那么就可以把 A 作为一个基类, 而把 B 作为基类的一个 派生类 (也称 子类). 派生类可以对基类:

  • 扩充: 在派生类中, 可以添加新的成员变量和成员函数
  • 修改: 在派生类中, 可以重新编写从基类继承得到的成员

派生类一经定义后, 可以独立使用, 不依赖于基类.

派生方式说明符:public, private, protected. 派生类的写法:

1
2
3
class Derived : public Base {
    // code
};

关于内存上, 派生类对象的大小等于基类对象的大小加上派生类对象自己的成员变量的大小. 在派生类对象中包含着基类对象, 且基类对象的存储位置位于派生类对象新增的成员变量之前.

覆盖

派生类可以定义一个和基类成员同名的成员, 这叫覆盖. 在派生类中访问这类成员时, 缺省的情况是访问派生类中定义的成员. 要在派生类中访问由基类定义的同名成员时, 要使用作用域符号::.

注意, 对于成员变量, 在内存中, 两个变量占用不同的空间. 并不建议覆盖成员变量.

关于权限及派生方式的访问权限, 可以参考下表:

派生方式\成员访问 public protected private
public public protected 不可访问
protected protected protected 不可访问
private private private 不可访问

为此, 可以用基类构造函数初始化派生类对象的基类部分. 例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Bug {
private:
    int nLegs;
    int nColor;
protected:
    int nType;
public:
    Bug(int legs, int color);
    void PrintBug() {}
};
class FlyBug : public Bug {
    int nWings;
public:
    FlyBug(int legs, int color, int wings);
};

Bug::Bug(int legs, int color) {
    nLegs = legs;
    nColor = color;
}
FlyBug::FlyBug(int legs, int color, int wings) : Bug(legs, color) {
    nType = 1;
    nWings = wings;
}

派生类的对象生命周期

在创建派生类的对象时, 需要调用基类的构造函数:初始化派生类对象中从基类继承的成员. 在执行一个派生类的构造函数之前, 总是先执行基类的构造函数. 派生类的析构函数被执行时, 执行完派生类的析构函数后, 自动调用基类的析构函数.

在创建派生类的对象时:

  • 先执行基类的构造函数, 用以初始化派生类对象中从基类继承的成员;
  • 再执行成员对象类的构造函数, 用以初始化派生类对象中成员对象;
  • 最后执行派生类自己的构造函数.

在派生类对象消亡时:

  • 先执行派生类自己的析构函数;
  • 再依次执行各成员对象类的析构函数;
  • 最后执行基类的析构函数.

总之, 析构函数的调用顺序与构造函数的调用顺序相反.

对于 public 继承具有赋值兼容规则, 即

1
2
3
4
class Base {};
class Derived : public Base {};
Base b;
Derived d;
  • 派生类对象可以赋值给基类对象

    1
    
    b = d; 
    
  • 派生类对象的地址可以赋值给基类指针

    1
    
    Base *pb = &d;
    
  • 派生类对象的引用可以赋值给基类引用

    1
    
    Base &rb = d;
    

注意, 只有 public 继承才具有赋值兼容规则, protectedprivate 不允许这种赋值方式.

指针强转成基类后, 就不再能访问派生类的成员了. 如果需要, 可以再利用指针强转回来.

1
2
3
Derived objDerived;
Base *ptrBase = &objDerived;
Derived *ptrDerived = (Derived *)ptrBase;

在声明派生类时,只需要列出它的直接基类, 派生类沿着类的层次自动向上继承它的间接基类. 构造的顺序即为派生类的继承顺序, 析构的顺序则相反.

本文遵循 CC BY-NC-SA 4.0 协议
使用 Hugo 构建