構造函數初始化列表以一個冒號開始,接着是以逗號分隔的數據成員列表,每個數據成員后面跟一個放在括號中的初始化式。例如:
class A { public: int a; float b; A(): a(0),b(9.9) {} //構造函數初始化列表 }; class A { public: int a; float b; A() //構造函數內部賦值 { a = 0; b = 9.9; } };
上面的例子中兩個構造函數的效果是一樣的。使用初始化列表的構造函數是顯示地初始化類的成員;而沒有使用初始化列表的構造函數是對類的成員賦值,並沒有顯示地初始化。
初始化列表的構造函數和內部賦值的構造函數對內置類型的成員沒有什么大的區別,像上面的任一個構造函數都可以。
用構造函數的初始化列表來進行初始化,寫法方便、簡練,尤其當需要初始化的數據成員較多時更顯其優越性。對非內置類型成員變量,推薦使用類構造函數初始化列表。
但有的時候必須用帶有初始化列表的構造函數:(1)沒有默認構造函數的成員類對象;(2)const成員或引用類型的成員。
構造函數中有着比我們所看見的還要多的細節,構造函數可以調用其它的構造函數來初始化對象中的基類對象和成員對象的構造函數。
類的數據成員中的其它類對象,若該成員類型是沒有默認構造函數,則必須進行顯示初始化,因為編譯器會隱式調用成員類型的默認構造函數,而它又沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。
例:
class A { public: A (int x) { i = x; // 無默認構造函數 } private: int i; }; class B { public: B(int y) { j = y; // error C2512: “A”: 沒有合適的默認構造函數可用 } private: A a; int j; }; int main( ) { B b(5); return 0; }
B類數據成員中有一個A類對象a,創建B類對象時,要先創建其成員對象a;A類有一個參數化大的構造函數,則編譯器不會提供默認無參數的構造函數,因此a無法創建。
對成員對象正確的初始化方法是通過顯示方式進行,B的構造函數應該寫成:
B(int y, int z) : a(z) { j = y; } B b(5,10);
構造函數初始化列表是初始化常數據成員和引用成員的唯一方式。因為const對象或引用類型只能初始化,不能對他們賦值。
class A { public: A (int x,int y) : c(x),j(y) // 構造函數初始化列表 { i = -1; } private: int i; const int c; int& j; }; int main( ) { int m; A a(5,m); return 0; }
若不通過初始化列表來對常數據成員和引用成員進行初始化:
class A { public: A (int x) // 構造函數初始化列表 { i = -1; c = 5; j = x; } private: int i; const int c; // error C2758: “A::c”: 必須在構造函數基/成員初始值設定項列表中初始化 int& j; // error C2758: “A::j”: 必須在構造函數基/成員初始值設定項列表中初始化 };
缺省情況下,在構造函數的被執行前,對象中的所有成員都已經被它們的缺省構造函數初始化了。當類中某個數據成員本身也是一個類對象時,我們應該避免使用賦值操作來對該成員進行初始化:
class Person { private: string name; public: Person(string& n) { name = n; } }
雖然這樣的構造函數也能達到正確的結果,但這樣寫效率並不高。當一個Person對象創建時,string類成員對象name先會被缺省構造函數進行初始化,然后在Person類構造函數中,它的值又會因賦值操作而在改變一次。我們可以通過初始化列表來顯示地對name進行初始化,這樣便將上邊的兩步(初始化和賦值)合並到一個步驟中了。
class Person { private: string name; public: Person(string& n): name(n){} }