C++派生類別建構函式和解構函式

2020-07-16 10:04:19
派生類物件中包含基礎類別物件,因此派生類物件在建立時,除了要呼叫自身的建構函式進行初始化外,還要呼叫基礎類別的建構函式初始化其包含的基礎類別物件。因此,程式中任何能夠生成派生類物件的語句,都要說明其包含的基礎類別物件是如何初始化的。

如果對此不做說明,則編譯器認為基礎類別物件要用無參建構函式初始化——如果基礎類別沒有無參建構函式,則會導致編譯錯誤。

在執行一個派生類別建構函式之前,總是先執行基礎類別的建構函式。

和封閉類說明成員物件如何初始化類似,派生類說明基礎類別物件如何初始化,也需要在建構函式後面新增初始化列表。在初始化列表中,要指明呼叫基礎類別建構函式的形式。具體寫法如下:

建構函式名(形參表): 基礎類別名(基礎類別建構函式實參表)
{
   ...
}

派生類物件消亡時,先執行派生類的解構函式,再執行基礎類別的解構函式。

下面的程式演示了派生類別建構函式和解構函式的呼叫順序:
#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

包含成員物件的派生類

在派生類也是封閉類的情況下,建構函式的初始化列表不但要指明基礎類別物件的初始化方式,還要指明成員物件的初始化方式。

派生類物件生成時,會引發一系列建構函式呼叫,順序是:先從上至下執行所有基礎類別的建構函式,再按照成員物件的定義順序執行各個成員物件的建構函式,最後執行自身的建構函式;而派生類物件消亡時,先執行自身的解構函式,然後按與構造的次序相反的順序依次執行所有成員物件的解構函式,最後再從底向上依次執行各個基礎類別的解構函式。