1. 本文目的:理清在各種繼承時,構造函數、復制構造函數、賦值操作符、析構函數的執行順序和執行內容。
2. 說明:雖然復制構造函數屬於構造函數的一種,有共同的地方,但是也具有一定的特殊性,所以在總結它的性質時將它單獨列出來了。
3. 單繼承、多繼承、虛繼承,既然都屬於繼承,那么雖然有一定的區別,但還是相同點比較多。如果放在一塊講,但為了將內容制作成遞進的,就分開了,對相同點進行重復,(大量的復制粘貼哈),但在不同點進行了標注。
注意:三塊內容是逐步遞進的
如果你懂虛函數,那么單繼承和多繼承那塊你就可以不看;
如果你懂多繼承,那單繼承你就不要看了,至於虛繼承就等你懂虛繼承再回來看吧;
如果你只懂單繼承,那你就只看單繼承就好。
1. 對於一個空類,例如:
1 class EmptyClass{};
雖然你沒有聲明任何函數,但是編譯器會自動為你提供下面這四個方法:
1 1.class EmptyClass { 2 2.public: 3 3. EmptyClass(); // 默認構造函數 4 4. EmptyClass(const EmptyClass &rhs); // 復制構造函數 5 5. ~EmptyClass(); // 析構函數 6 6. EmptyClass& operator=(const EmptyClass &rhs); // 賦值運算符 7 7.}
對於這四個方法的任何一個,你的類如果沒有聲明,那么編譯器就會自動為你對應的提供一個默認的。(在《C++ primer》中,這個編譯器自動提供的版本叫做“合成的***”,例如合成的復制構造函數)當然如果你顯式聲明了,編譯器就不會再提供相應的方法。
1. 合成的默認構造函數執行內容:如果有父類,就先調用父類的默認構造函數。
2. 合成的復制構造函數執行內容:使用參數中的對象,構造出一個新的對象。
3. 合成的賦值操作符執行內容:使用參數中的對象,使用參數對象的非static成員 依次對 目標對象的成員賦值。注意:在賦值操作符執行之前,目標對象已經存在。
4. 在繼承體系中,要將基類(或稱為父類)的析構函數,聲明為virtual方法(即虛函數)。
5. 子類中包含父類的成員。即子類有兩個部分組成,父類部分和子類自己定義的部分。
6. 如果在子類中顯式調用父類的構造函數,只能在構造函數的初始化列表中調用,並且只能調用其直接父類的。
7. 在多重繼承時,按照基類繼承列表中聲明的順序初始化父類。
8. 在虛繼承中,虛基類的初始化 早於 非虛基類,並且子類來初始化虛基類(注意:虛基類不一定是子類的直接父類)。
單繼承
核心:在構造子類之前一定要執行父類的一個構造函數。
1.構造函數(不包括復制構造函數)。
順序:①直接父類;②自己
注意:若直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果沒有顯式定義構造函數,則“合成的默認構造函數”會自動調用直接父類的“默認構造函數”,然后調用編譯器為自己自動生成的“合成的默認構造函數”。
2.2 如果顯式定義了自己的構造函數
2.2.1 如果沒有顯式調用直接父類的任意一個構造函數,那么和“合成的默認構造函數”一樣,會先自動調用直接父類的 默認構造函數,然后調用自己的構造函數。
2.2.2 如果顯式調用了直接父類的任意一個構造函數,那么會先調用直接父類相應的構造函數,然后調用自己的構造函數。
2. 復制構造函數
順序:①直接父類;②自己
注意:和構造函數一樣,若直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果 沒有顯式定義復制構造函數,則“合成的復制構造函數”會自動調用直接父類的“復制構造函數”,然后調用編譯器為自己自動生成的“合成的復制構造函數”(注意:不是默認構造函數)
2.2 如果顯式定義了自己的復制構造函數 (和構造函數類似)
2.2.1 如果沒有顯式調用父類的任意一個構造函數,那么會先調用直接父類的 默認構造函數(注意:不是 復制構造函數)。
2.2.2 如果顯式調用了直接父類的任意一個構造函數,那么會先調用直接父類相應的構造函數。
3.賦值操作符重載
3.1 如果沒有顯式定義,會自動調用直接父類的賦值操作符。(注意:不是 默認構造函數)
3.2 如果顯式定義了,就只執行自己定義的版本,不再自動調用直接父類的賦值操作符,只執行自己的賦值操作符。
注意:如有需要對父類子部分進行賦值,應該在自己編寫的代碼中,顯式調用父類的賦值操作符。
4. 析構函數
與構造函數 順序相反。
多繼承
和單繼承的差別就是:需要考慮到多個直接父類。其它的都相同
1.構造函數(不包括復制構造函數)。
順序:①所有直接父類;(按照基類繼承列表中聲明的順序)②自己
注意:若直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果 沒有 顯式定義構造函數,則“合成的默認構造函數”會自動依次調用所有直接父類的“默認構造函數”,然后調用編譯器為自己自動生成的“合成的默認構造函數”。
2.2 如果顯式定義了自己的構造函數
2.2.1 如果沒有顯式調用父類的任意一個構造函數,那么和“合成的默認構造函數”一樣,會自動依次調用所有直接父類的 默認構造函數,然后調用自己的構造函數。
2.2.2 如果顯式調用了父類的任意一個構造函數,那么按照基類列表的順序,對於每一個父類依次判斷:若顯式調用了構造函數,那么會調用該父類相應的構造函數;如果沒有顯式調用,就調用默認構造函數。最后調用自己的構造函數。
復制構造函數
順序:①所有直接父類;(按照基類繼承列表中聲明的順序)②自己
注意:和構造函數一樣,若直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果 沒有顯式定義復制構造函數,則“合成的復制構造函數”會自動依次調用所有直接父類的“復制構造函數”,然后調用編譯器為自己自動生成的“合成的復制構造函數”(注意:不是默認構造函數)
2.2 如果顯式定義了自己的復制構造函數 (和構造函數類似)
2.2.1 如果沒有顯式調用父類的任意一個構造函數,那么會先自動依次調用直接父類的 默認構造函數(注意:不是 復制構造函數)。
2.2.2 如果顯式調用了直接父類的任意一個構造函數,那么按照基類列表的順序,對於每一個父類依次判斷:若顯式調用了構造函數,那么會調用該父類相應的構造函數;如果沒有顯式調用,就調用默認構造函數。最后調用自己的復制構造函數。
3.賦值操作符重載
3.1 如果沒有顯式定義,會自動依次調用直接父類的賦值操作符。(注意:不是 默認構造函數)
3.2 如果顯式定義了,就只執行自己定義的版本,不再自動調用直接父類的賦值操作符,只執行自己的賦值操作符。
注意:如有需要對父類子部分進行賦值,應該在自己編寫的代碼中,顯式調用所有直接父類的賦值操作符。
4. 析構函數
與 構造函數 順序相反。
虛繼承
和多繼承的差別就是:要考慮到虛基類,其它的都相同。(虛基類的初始化要早於非虛基類,並且只能由子類對其進行初始化)
1.構造函數(不包括復制構造函數)。
順序:①所有虛基類(按照基類繼承列表中聲明的順序進行查找);②所有直接父類;(按照基類繼承列表中聲明的順序)③自己
注意:若虛基類或者直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造,“虛基類的父類”也會在“虛基類”之前構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果 沒有 顯式定義構造函數,則“合成的默認構造函數”會先依次調用所有虛基類的默認構造函數,然后再自動依次調用所有直接父類的“默認構造函數”,最后調用編譯器為自己自動生成的“合成的默認構造函數”。
2.2 如果顯式定義了自己的構造函數 2.2.1 如果沒有顯式調用父類的任意一個構造函數,那么和“合成的默認構造函數”一樣,會先依次調用所有虛基類的默認構造函數,然后再自動依次調用所有直接父類的 默認構造函數,最后調用自己的構造函數。
2.2.2 如果顯式調用了父類的任意一個構造函數,那么按照基類列表的順序,先初始化所有虛基類,再初始化所有直接父類。對於每一個父類依次判斷:若顯式調用了構造函數,那么會調用該父類相應的構造函數;如果沒有顯式調用,就調用默認構造函數。最后調用自己的構造函數。
2. 復制構造函數
順序:①所有虛基類(按照基類繼承列表中聲明的順序進行查找);②所有直接父類;(按照基類繼承列表中聲明的順序)③自己
注意:和構造函數一樣,若虛基類或者直接父類還有父類,那么“直接父類的父類”會在“直接父類” 之前 構造,“虛基類的父類”也會在“虛基類”之前構造。 可以理解為這是一個遞歸的過程,知道出現一個沒有父類的類才停止。
2.1 如果 沒有顯式定義復制構造函數,則“合成的復制構造函數”會自動依次調用所有直接父類的“復制構造函數”,然后調用編譯器為自己自動生成的“合成的復制構造函數”(注意:不是默認構造函數)
2.2 如果顯式定義了自己的復制構造函數 (和構造函數類似)
2.2.1 如果沒有顯式調用父類的任意一個構造函數,那么會先依次調用所有虛基類的默認構造函數,然后再依次調用所有直接父類的 默認構造函數(注意:不是 復制構造函數)。
2.2.2 如果顯式調用了直接父類的任意一個構造函數,那么按照基類列表的順序,先初始化所有虛基類,再初始化所有直接父類。對於每一個父類依次判斷:若顯式調用了構造函數,那么會調用該父類相應的構造函數;如果沒有顯式調用,就調用默認構造函數。
3.賦值操作符重載
3.1 如果沒有顯式定義,會自動依次調用所有虛基類和所有直接父類的賦值操作符。(注意:不是 默認構造函數)
3.2 如果顯式定義了,就只執行自己定義的版本,不再自動調用直接父類的賦值操作符,只執行自己的賦值操作符。
注意:如有需要對父類子部分進行賦值,應該在自己編寫的代碼中,顯式調用所有虛基類和所有直接父類的賦值操作符。
4. 析構函數
與 構造函數 順序相反。
總結:
1. 整體順序:虛基類 --> 直接父類 -->自己
2. 在任何顯式定義的構造函數中,如果沒有顯式調用父類的構造函數,那么就會調用父類的默認構造函數。
3. 合成的復制構造函數、合成的賦值操作符,(當沒有顯式定義時,編譯器自動提供),會自動調用的是虛基類和直接父類的復制構造函數和賦值操作符,而不是默認構造函數;
4. 自己顯式定義的復制構造函數,除非在初始化列表中顯示調用,否則只會調用虛基類和父類的默認構造函數。
5. 自己顯式定義的賦值操作符,除非顯式調用,否則只執行自己的代碼。
6. 析構函數的執行順序與 構造函數 相反。
