C++建構函式和解構函式(詳解版)

2020-07-16 10:04:39
本節將詳細給大家介紹 C++ 類中的建構函式解構函式

建構函式

建構函式是一個特殊的公共成員函數,它在建立類物件時會自動被呼叫,用於構造類物件。如果程式設計師沒有編寫建構函式,則 C++ 會自動提供一個,這個自動提供的建構函式永遠不會有人看到它,但是每當程式定義一個物件時,它會在後台靜默執行。

當然,程式設計師在建立類時通常會編寫自己的建構函式。如果這樣做,那麼除了構建類的每個新建立的物件之外,它還將執行程式設計師寫入其中的其他任何程式碼。程式設計師通常使用建構函式來初始化物件的成員變數。但實際上,它可以做任何正常函數可以做的事情。

建構函式看起來像一個常規函數,除了它的名稱必須與它所屬類的名稱相同,這就是為什麼編譯器知道這個特定的成員函數是一個建構函式。此外,建構函式不允許有返回型別。

下面的程式包括一個名為 Demo 的類,其建構函式除了列印訊息之外什麼都不做。編寫它的目的就是為了演示建構函式何時執行。因為 Demo 物件是在兩個 cout 語句之間建立的,所以建構函式將在這兩個語句生成的輸出行之間列印它的訊息。
// This program demonstrates when a constructor executes.
#include <iostream>
using namespace std;

class Demo
{
    public:
        Demo()
        {
            cout << "Now the constructor is running.n";
        }
};

int main()
{
    cout << "This is displayed before the object is created. n";
    Demo demoObj;    // Define a Demo object
    cout << "This is displayed after the object is created.n";
    return 0;
}
程式輸出結果為:

This is displayed before the object is created.
Now the constructor is running.
This is displayed after the object is created.

程式中,將建構函式定義為類宣告中的行內函式。當然,像任何其他類成員函數一樣,也可以將其原型放在類宣告中,然後將其定義在類之外。在這種情況下,需要新增函數所屬類的名稱和函數名前面的作用域解析運算子。但是由於建構函式的名稱與類名相同,所以名稱會出現兩次。

以下就是在類宣告之外定義 Demo 建構函式時,其函數頭的樣子:
Demo:: Demo ()    // 建構函式
{
    cout << "Now the constructor is running. n";
}

過載建構函式

我們知道,當兩個或多個函數共用相同的名稱時,函數名稱被稱為過載。只要其形參列表不同,C++ 程式中可能存在具有相同名稱的多個函數。

任何類成員函數都可能被過載,包括建構函式。例如,某個建構函式可能需要一個整數實參,而另一個建構函式則需要一個 double,甚至可能會有第三個建構函式使用兩個整數。只要每個建構函式具有不同的形參列表,則編譯器就可以將它們分開。

下面的程式宣告並使用一個名為 Sale 的類,它有兩個建構函式。第一個建構函式的形參接受銷售稅率;第二個建構函式是免稅銷售,沒有形參。它將稅率設定為 0。這樣一個沒有形參的建構函式稱為預設建構函式。
#include <iostream>
#include <iomanip>
using namespace std;

// Sale class declaration
class Sale
{
    private:
        double taxRate;
    public:
        Sale(double rate) // Constructor with 1 parameter
        {
            taxRate = rate; // handles taxable sales
        }
        Sale ()    // Default constructor
        {
            taxRate = 0.0    // handles tax-exempt sales
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost*taxRate;
            return total;
        }
};

int main()
{
    Sale cashier1(.06); // Define a Sale object with 6% sales tax
    Sale cashier2;    // Define a tax-exempt Sale object
    // Format the output
    cout << fixed << showpoint << setprecision (2);
    // Get and display the total sale price for two $24.95 sales
    cout << "With a 0.06 sales tax rate, the totaln";
    cout << "of the $24.95 sale is $";
    cout << cashier1.calcSaleTotal(24.95) << endl;
    cout << "nOn a tax-exempt purchase, the totaln";
    cout << "of the $24.95 sale is, of course, $";
    cout << cashier2.calcSaleTotal(24.95) << endl;
    return 0;
}
程式輸出結果:

With a 0.06 sales tax rate, the total
of the $24.95 sale is $26.45
On a tax-exempt purchase, the total
of the $24.95 sale is, of course, $24.95

注意看此程式如何定義的兩個 Sale 物件:

Sale cashier1(.06);
Sale cashier2;

在 cashier1 的名稱後面有一對括號,用於儲存值,傳送給有一個形參的建構函式。但是,在 Cashier2 的名稱後面就沒有括號,它不傳送任何引數。在 C++ 中,當使用預設建構函式定義物件時,不用傳遞實參,所以不能有任何括號,即:

Sale cashier2 () ;    // 錯誤
Sale cashier2;    // 正確

預設建構函式

Sale 類需要一個預設建構函式來處理免稅銷售的情況,但是其他類可能並不需要這樣一個建構函式。例如,如果通過類建立的物件總是希望將實參傳遞給建構函式。那麼,在設計一個具有建構函式的類時,應該包括一個預設建構函式,這在任何時候都會被認為是一個很好的程式設計實踐。

如果沒有這樣一個預設建構函式,那麼當程式嘗試建立一個物件而不傳遞任何引數時,它將不會編譯,這是因為必須有一個建構函式來建立一個物件。為了建立不傳遞任何引數的物件,必須有一個不需要引數的建構函式,也就是預設建構函式。

如果程式設計師沒有為類編寫任何建構函式,則編譯器將自動為其建立一個預設建構函式。但是,當程式設計師編寫了一個或多個建構函式時,即使所有這些建構函式都是有引數的,編譯器也不會建立一個預設建構函式,所以程式設計師有責任這樣做。

類可能有許多建構函式,但只能有一個預設建構函式。這是因為:如果多個函數具有相同的名稱,則在任何給定時刻,編譯器都必須能夠從其形參列表中確定正在呼叫哪個函數。它使用傳遞給函數的實參的數量和型別來確定要呼叫的過載函數。因為一個類名稱只能有一個函數能夠接受無引數,所以只能有一個預設建構函式。

一般情況下,就像在 Sale 類中那樣,預設建構函式沒有形參。但是,也可能有另一種的預設建構函式,其所有形參都具有預設值,所以,它也可以無實參呼叫。如果建立了一個接受無實參的建構函式,同時又建立了另外一個有引數但允許所有引數均為預設值的建構函式,那麼這將是一個錯誤,因為這實際上是建立了兩個“預設”建構函式。以下語句就進行了這種非法的宣告:
class Sale //非法宣告
{
    private:
        double taxRate;
    public:
        Sale()    //無實參的預設建構函式
        {
            taxRate = 0.05;
        }
        Sale (double r = 0.05)    //有預設實參的預設建構函式
        {
            taxRate = r;
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost * taxRate;
            return total;
        };
};
可以看到,第一個建構函式沒有形參,第二個建構函式有一個形參,但它有一個預設實參。如果一個物件被定義為沒有參數列,那麼編譯器將無法判斷要執行哪個建構函式。

解構函式

解構函式是具有與類相同名稱的公共成員函數,前面帶有波浪符號(?)。例如,Rectangle 類的解構函式將被命名為 ?Rectangle。

當物件被銷毀時,會自動呼叫解構函式。在建立物件時,建構函式使用某種方式來進行設定,那麼當物件停止存在時,解構函式也會使用同樣的方式來執行關閉過程。例如,當具有物件的程式停止執行或從建立物件的函數返回時,就會發生這種情況。

下面的程式顯示了一個具有建構函式和解構函式的簡單類。它說明了在程式執行過程中這兩個函數各自被呼叫的時間:
//This program demonstrates a destructor.
#include <iostream>
using namespace std;

class Demo
{
    public:
        Demo(); // Constructor prototype
        ~Demo(); // Destructor prototype
};

Demo::Demo()    // Constructor function definition
{
    cout << "An object has just been defined,so the constructor" << " is running.n";
}

Demo::~Demo() // Destructor function definition
{
    cout << "Now the destructor is running.n";
}

int main()
{
    Demo demoObj;    // Declare a Demo object;
    cout << "The object now exists, but is about to be destroyed.n";
    return 0;
}
程式輸出結果:

An object has just been defined, so the constructor is running.
The object now exists, but is about to be destroyed.
Now the destructor is running.

除了需要知道在物件被銷毀時會自動呼叫解構函式外,還應注意以下事項:
  1. 像建構函式一樣,解構函式沒有返回型別。
  2. 解構函式不能接收實參,因此它們從不具有形參列表。
  3. 由於解構函式不能接收實參,因此只能有一個解構函式。