父類子類的拷貝構造與賦值


 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. 析構函數的執行順序與 構造函數 相反。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM