C++派生类的构造函数和析构函数

派生类对象中包含基类对象,因此派生类对象在创建时,除了要调用自身的构造函数进行初始化外,还要调用基类的构造函数初始化其包含的基类对象。因此,程序中任何能够生成派生类对象的语句,都要说明其包含的基类对象是如何初始化的。

如果对此不做说明,则编译器认为基类对象要用无参构造函数初始化——如果基类没有无参构造函数,则会导致编译错误。

在执行一个派生类的构造函数之前,总是先执行基类的构造函数。

和封闭类说明成员对象如何初始化类似,派生类说明基类对象如何初始化,也需要在构造函数后面添加初始化列表。在初始化列表中,要指明调用基类构造函数的形式。具体写法如下:

构造函数名(形参表): 基类名(基类构造函数实参表)
{
   ...
}

派生类对象消亡时,先执行派生类的析构函数,再执行基类的析构函数。

下面的程序演示了派生类的构造函数和析构函数的调用顺序:
#include <iostream>
#include <string>
using namespace std;
class CBug {
    int legNum, color;
public:
    CBug(int ln, int c1) : legNum(ln), color(c1)
    {
        cout << "CBug Constructor" << endl;
    };
    ~CBug()
    {
        cout << "CBug Destructor" << endl;
    }
    void Printlnfo()
    {
        cout << legNum << "," << color << endl;
    }

};
class CFlyingBug : public CBug
{
    int wingNum;
public:
    //CFlyingBug(){}  若不注释掉则会编译出错
    CFlyingBug::CFlyingBug(int ln, int c1, int wn) : CBug(ln, c1), wingNum(wn)
    {
        cout << "CFlyingBug Constructor" << endl;
    }
    ~CFlyingBug()
    {
        cout << "CFlyingBug Destructor" << endl;
    }
};
int main() {
    CFlyingBug fb(2, 3, 4);
    fb.Printlnfo();
    return 0;
}
程序输出结果:
CBug Constructor
CFlyingBug Constructor
2,3
CFlyingBug Destructor
CBug Destructor

第 25 行如果没有注释掉会编译出错。因为这个构造函数没有说明在派生类对象用该构造函数初始化的情况下,其基类对象该如何初始化——这也就意味着基类对象应该用无参构造函数初始化,可是 CBug 类并没有无参构造函数,所以编译会出错。

第 26 行中的“CBUg(ln, c1)”指明了在派生类对象用该构造函数初始化的情况下,其基类对象的初始化方式。

思考题:派生类对象生成时要先执行基类构造函数,消亡时要先执行自身析构函数,再执行基类析构函数,为什么?

和封闭类的情况类似,如果一个派生类对象是用默认复制构造函数初始化的,那么它内部包含的基类对象也要用基类的复制构造函数初始化。

多层次的派生

C++ 中,派生可以是多层次的。例如学生类派生出中学生类,中学生类又派生出初中生类和高中生类。总之,类 A 派生类 B,类 B 可再派生类 C,类 C 又能派生类 D,以此类推。

这种情况下,称类 A 是类 B 的直接基类,类 B 是类 C 的直接基类,类 A 是类 C 的间接基类。当然,类 A 也是类 D 的间接基类。在定义派生类时,只写直接基类,不写间接基类。派生类沿着类的层次自动向上继承它所有的间接基类。

派生类的成员包括派生类自己定义的成员、直接基类中定义的成员,以及所有间接基类的全部成员。

当派生类的对象生成时,会从最顶层的基类开始逐层往下执行所有基类的构造函数,最后再执行自身的构造函数;当派生类对象消亡时,会先执行自身的析构函数,然后从底向上依次执行各个基类的析构函数。

例如下面的程序:
#include <iostream>
using namespace std;
class A {
public:
    int n;
    A(int i) :n(i) { cout << "A " << n << " constructed" << endl; }
    ~A() { cout << "A " << n << " destructed" << endl; }
};
class B :public A
{
public:
    B(int i) :A(i) { cout << "B constructed" << endl; }
    ~B() { cout << "B destructed" << endl; }

};
class C :public B {
public:
    C() :B(2) { cout << "B constructed" << endl; }
    ~C() { cout << "B destructed" << endl; }
};
int main()
{
    C Obj;
    return 0;
}
程序的输出结果:
A 2 constructed
B constructed
B constructed
B destructed
B destructed
A 2 destructed

包含成员对象的派生类

在派生类也是封闭类的情况下,构造函数的初始化列表不但要指明基类对象的初始化方式,还要指明成员对象的初始化方式。

派生类对象生成时,会引发一系列构造函数调用,顺序是:先从上至下执行所有基类的构造函数,再按照成员对象的定义顺序执行各个成员对象的构造函数,最后执行自身的构造函数;而派生类对象消亡时,先执行自身的析构函数,然后按与构造的次序相反的顺序依次执行所有成员对象的析构函数,最后再从底向上依次执行各个基类的析构函数。