構造函數的工作是保證每個對象的數據成員具有合適的初始值。
一、構造函數的定義
(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();
以上兩種都是使用默認構造函數定義一個對象的正確方式。