原文:https://zhuanlan.zhihu.com/p/33004628
下面我們先看例子
1 #include <iostream> 2 using namespace std; 3 class Base 4 { 5 public: 6 Base(int val) 7 { 8 m_num = 0; 9 cout << "create Base(int val)" << endl; 10 } 11 private: 12 int m_num; 13 };
上邊的代碼,我先定義了一個Base類,並且定義了有一個整型實參的構造函數Base(int val)
1 class BaseChild: public Base 2 { 3 public: 4 BaseChild() 5 { 6 m_num = 0; 7 cout << "create is BaseChild()" << endl; 8 } 9 10 private: 11 int m_num; 12 }; 13 14 int main(int argc, char *argv[]) 15 { 16 BaseChild child; 17 }
上邊的代碼繼承Base,定義了它的默認構造函數
並且在主函數中創建BaseChild的對象child
編譯但報如下錯誤:
這意思是說,沒有Base的默認構造函數。
結論1:如果沒有定義任何構造函數,C++編譯器會自動創建一個默認構造函數。
結論2:如果已經定義了一個構造函數,編譯器不會自動創建默認構造函數,只能顯式調用該構造函數。
在C++中,當創建一個對象時,編譯器要保證調用了所有子對象的構造函數,這是C++強制要求的,也是它的一個機制。
因為在Base中沒有定義默認構造函數,只定義了一個有整型參數的構造函數,因此編譯器並不會再去生成一個默認的構造函數,而BaseChild繼承Base時,又沒有顯式地指定Base的構造函數,所以編譯報錯。
如果我們不修改Base,那么,我們用什么辦法不去調用默認構造函數,而是顯式的調用Base帶參構造函數呢。答案就是初始化列表。
C++就為我們提供了這樣的語法。即在冒號和這個構造函數定義體的左括號之間可指定基類構造函數,如下:
1 BaseChild():Base(1) 2 { 3 cout << "create is BaseChild()" << endl; 4 }
現在,再編譯程序,輕松通過。
當然,初始化列表還可以對類本身的數據成員進行初始化,如對BaseChild成員m_num進行初始化:
1 BaseChild():Base(1), m_num(0){...}
中間要以逗號隔開。
細心的同學,可能會提問,我們平常見到的都是
int m_num = 0;
而剛剛的代碼是m_num(0),這是正確的,我們可以認為這就是調用了int類型的構造函數。類似的,new int(2)是一樣的道理。
上面是整數類型的賦值,那么,如果是對象之間的賦值呢,例如:
BaseChild child = BaseChild();
其實,這又涉及了另外一個話題,賦值構造函數和編譯器的優化。
其具體執行順序是:
1調用BaseChild構造函數,生成一個臨時對象
2給child成員賦值
3創建child對象后,刪除臨時對象
那么,針對上面的順序,編譯器有可能會優化代碼為BaseChild child()直接創建child對象。
最后,總結一下初始化列表吧:
1 因為初始化列表中無法直接初始化基類的數據成員,所以你需要在列表中指定基類的構造函數,如果不指定,編譯器則會調用基類的默認構造函數。
2 推薦使用初始化列表,它會比在函數體內初始化派生類成員更快,這是因為在分配內存后,在函數體內又多進行了一次賦值操作。
3 初始化列表並不能指定初始化的順序,正確的順序是,首先初始化基類,其次根據派生類成員聲明次序依次初始化。