C++建構函式

QingLiXueShi發表於2015-02-14

建構函式的工作是保證每個物件的資料成員具有合適的初始值。

一、建構函式的定義

(1建構函式可以被過載。

可以為一個類宣告的建構函式的數量沒有限制,只要每個建構函式的形參表是唯一的。

(2實參決定使用哪個建構函式。

(3建構函式自動執行。

只要建立該型別的一個物件,編譯器就執行一個建構函式。

(4建構函式不能宣告為const。

建立類型別的const物件時,執行一個普通建構函式來初始化該const物件。建構函式的工作是初始化該物件,不管物件是否為const,都用一個建構函式來初始化該物件。

 

二、建構函式初始化式

建構函式可以包含一個初始化列表。

Sales_item::Sales_item(const string &book) : 
    isbn(book), units_sold(0), revenue(0.0) {}

注意:建構函式可以定義在類的內部或外部。建構函式初始化列表只在建構函式的定義中而不是宣告中指定。

 

建構函式初始化列表難以理解的一個原因在於,省略初始化列表並在建構函式的函式體內對資料成員賦值是合法的。

Sales_item::Sales_item(const string &book)
{
    isbn = book;
    units_sold = 0;
    revenue = 0.0;
}

上面建構函式給成員賦值,但沒有進行顯示初始化。

注意:

(1)不管是否有顯式初始化式,在執行建構函式前,要初始化isbn成員。這個建構函式隱式使用string預設建構函式初始化isbn。執行建構函式的函式體時,isbn成員已經有值了,該值被建構函式函式體中的賦值所覆蓋。

(2)建構函式分兩個階段執行:1)初始化階段;2)普通的計算階段。計算階段由建構函式函式體中的所有語句組成。

(3)不管成員是否在建構函式初始化列表中顯示初始化,類型別的資料成員總是在初始化階段初始化。初始化發生在計算階段開始前。

(4)在建構函式初始化列表中沒有顯示提及的每個成員,將進行如下處理:如果是類型別,執行該型別預設建構函式來初始化該型別的成員;如果是內建型別,依賴於物件的作用域。全域性作用域中它們被初始化為0,區域性作用域中不被初始化。

 

1、有時需要建構函式初始化列表。

如果沒有為類成員提供初始化式,則編譯器會隱式地使用成員的預設建構函式初始化該成員。如果這個類沒有預設建構函式,則編譯器嘗試使用預設建構函式將會失敗。在這種情況下,為了初始化成員,必須提供初始化式。

注意:

(1)有些成員必須在建構函式初始化列表中進行初始化。對於這樣的成員,在建構函式函式體中對它們賦值不起作用。沒有預設建構函式的類型別的成員,以及const或引用型別的成員,不管是哪種型別,都必須在建構函式初始化列表中進行初始化。

(2)除了兩種情況,對非類型別的資料成員進行賦值或使用初始化式在結果和效能上都是等價的。下面的建構函式是錯誤的。

class ConstRef
{
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
};

ConstRef::ConstRef(int ii)
{
    i = ii;        //OK
    ci = ii;    //不能對const賦值
    ri = i;        //
}

可以初始化const物件或引用型別的物件,但不能對它們賦值。初始化const或引用型別資料成員的唯一機會是在建構函式初始化列表中。下面的建構函式是正確的。

ConstRef::ConstRef(int ii) : i(ii), ci(ii), ri(ii) {}

注意:在許多類中,初始化和賦值嚴格來說都是低效率的:資料成員可能已經被直接初始化了,還要對它進行初始化和賦值。比效率問題更重要的是,某些資料成員必須要初始化。

 

2、成員初始化的次序。

每個成員在建構函式初始化列表中只能指定一次。建構函式初始化列表僅指定用於初始化成員的值,並不指定這些初始化執行的次序。成員被初始化的次序就是定義成員的次序。

注意:初始化的次序常常無關緊要。然而,如果一個成員是根據其他成員而初始化,則成員初始化的次序是至關重要的。

class X
{
    int i;
    int j;
public:
    X(int val) : j(val), i(j) {} 
};

這個初始化列表的效果是用尚未初始化的j值來初始化i。

注意:按照與成員宣告一致的次序編寫建構函式初始化列表是個好主意。此外,儘可能避免使用成員來初始化其他成員。

 

3、初始化式可以是任意表示式。

一個初始化式可以是任意複雜的表示式。

Sales_item(const string &book, int cnt, double price) : 
    isbn(book), units_sold(cnt), revenue(cnt * price) {}

 

4、類型別的資料成員的初始化式。

初始化類型別的成員時,要指定實參並傳遞給成員型別的一個建構函式。可以使用該型別的任意建構函式。

Sales_item() : isbn(10, '9'), units_sold(0), revenue(0.0) {}

 

三、預設實參與建構函式

使用預設實參可減少程式碼重複。

 

四、預設建構函式

只要定義一個物件時沒有提供初始化式,就使用預設建構函式。為所有形參提供預設實參的建構函式也定義了預設建構函式。

1、合成的預設建構函式。

一個類哪怕只定義了一個建構函式,編譯器也不會再生成預設建構函式。只有當一個類沒有定義建構函式時,編譯器才會自動生成一個預設建構函式。

注意:

(1)合成的預設建構函式使用與變數初始化相同的規則來初始化成員。具有類型別的成員通過執行各自的預設建構函式進行初始化。內建和複合型別的成員,只對定義在全域性作用域中的物件才初始化。

(2)如果類包含內建或複合型別的成員,則該類不應該依賴於合成的預設建構函式。它應該定義自己的建構函式來初始化這些成員。

 

2、類通常應定義一個預設建構函式。

在某些情況下,預設建構函式是由編譯器隱式應用的。如果類沒有預設建構函式,則該類就不能用在這些環境中。例如:NoDefault類沒有預設建構函式,有一個接受string實參的建構函式。

(1)具有NoDefault成員的類的每個建構函式,必須通過傳遞一個初始的string值給NoDefault建構函式來顯示初始化NoDefault成員。

(2)編譯期不會為具有NoDefault型別成員的類合成預設建構函式。如果這樣的類希望提供預設建構函式,就必須顯式定義,並且預設建構函式必須顯式初始化其NoDefault成員。

(3)NoDefault型別不能用作動態分配陣列的元素型別。

(4)NoDefault型別的靜態分配陣列必須為每個元素提供一個顯示的初始化。

(5)如果有一個儲存NoDefault物件的容器,就不能使用接受容器大小而沒有同時提供一個元素初始化式的建構函式。

注意:通常如果定義了其他建構函式,則提供一個預設建構函式總是對的。在預設建構函式中給成員提供的初始值應該指出該物件是“空”的。

 

3、使用預設建構函式。

Sales_item myobj;
Sales_item myobj = Sales_item();

以上兩種都是使用預設建構函式定義一個物件的正確方式。

相關文章