1. 程式人生 > >C++在單繼承、多繼承、虛繼承時,建構函式、複製建構函式、賦值操作符、解構函式的執行順序和執行內容

C++在單繼承、多繼承、虛繼承時,建構函式、複製建構函式、賦值操作符、解構函式的執行順序和執行內容

一、本文目的與說明

    1. 本文目的:理清在各種繼承時,建構函式、複製建構函式、賦值操作符、解構函式的執行順序執行內容

    2. 說明:雖然複製建構函式屬於建構函式的一種,有共同的地方,但是也具有一定的特殊性,所以在總結它的性質時將它單獨列出來了。

    3. 單繼承、多繼承、虛繼承,既然都屬於繼承,那麼雖然有一定的區別,但還是相同點比較多。如果放在一塊講,但為了將內容製作成遞進的,就分開了,對相同點進行重複,(大量的複製貼上哈),但在不同點進行了標註。 
        注意:三塊內容是逐步遞進的 
                     如果你懂虛擬函式,那麼單繼承和多繼承那塊你就可以不看; 
                     如果你懂多繼承,那單繼承你就不要看了,至於虛繼承就等你懂虛繼承再回來看吧; 
                     如果你只懂單繼承,那你就只看單繼承就好。

二、基本知識

    1. 對於一個空類,例如;

  1. class EmptyClass{};  

        雖然你沒有宣告任何函式,但是編譯器會自動為你提供上面這四個方法。

  1. class EmptyClass {  
  2. public:  
  3.     EmptyClass();                        //  預設建構函式
  4.     EmptyClass(const EmptyClass &rhs);    //  複製建構函式
  5.     ~EmptyClass();                       // 解構函式
  6.     EmptyClass& operator=(const
     EmptyClass &rhs);    //  賦值運算子
  7. }  

        對於這四個方法的任何一個,你的類如果沒有宣告,那麼編譯器就會自動為你對應的提供一個預設的。(在《C++ primer》中,這個編譯器自動提供的版本叫做“合成的***”,例如合成的複製建構函式)當然如果你顯式聲明瞭,編譯器就不會再提供相應的方法。

    2. 合成的預設建構函式執行內容:如果有父類,就先呼叫父類的預設建構函式。

    3. 合成的複製建構函式執行內容:使用引數中的物件,構造出一個新的物件。

    4. 合成的賦值操作符執行內容:使用引數中的物件,使用引數物件的非static成員 依次對 目標物件的成員賦值。注意:在賦值操作符執行之前,目標物件已經存在。

    5. 在繼承體系中,要將基類(或稱為父類)的解構函式,宣告為virtual方法(即虛擬函式)。

    6. 子類中包含父類的成員。即子類有兩個部分組成,父類部分和子類自己定義的部分。

    7. 如果在子類中顯式呼叫父類的建構函式,只能在建構函式的初始化列表中呼叫,並且只能呼叫其直接父類的。

    8. 在多重繼承時,按照基類繼承列表中宣告的順序初始化父類。

    9. 在虛繼承中,虛基類的初始化 早於 非虛基類,並且子類來初始化虛基類(注意:虛基類不一定是子類的直接父類)。

三、單繼承

核心:在構造子類之前一定要執行父類的一個建構函式。

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. 複製建構函式

               順序:①所有直接父類;(按照基類繼承列表中宣告的順序)②自己  
            注意:和建構函式一樣,若直接父類還有父類,那麼“直接父類的父類”會在“直接父類” 之前 構造。 可以理解為這是一個遞迴的過程,知道出現一個沒有父類的類才停止。

    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. 解構函式的執行順序與 建構函式 相反。

七、例子程式

話說只有自己寫一個程式,然後研究執行結果,才會掌握的更好。所以下面就是個例子程式了。可以根據需要,註釋掉某個類的相應函式,觀察結果。

1. 該例子的繼承層次圖為:(M和N是虛基類)



ClassDiagram2

2. 程式碼如下

  1. #include <iostream>
  2. usingnamespace std;  
  3. class A {  
  4. public:  
  5.     A() {  
  6.         cout<<"int A::A()"<<endl;  
  7.     }  
  8.     A(A &a) {  
  9.         cout<<"int A::A(A &a)"<<endl;  
  10.     }  
  11.     A& operator=(A& a) {  
  12.         cout<<"int A::operator=(A &a)"<<endl;  
  13.         return a;  
  14.     }  
  15.     virtual ~A() {  
  16.         cout<<"int A::~A()"<<endl;  
  17.     }  
  18. };  
  19. class M :public A {  
  20. public:  
  21.     M() {  
  22.         cout<<"int M::M()"<<endl;  
  23.     }  
  24.     M(M &a) {  
  25.         cout<<"int M::M(M &a)"<<endl;  
  26.     }  
  27.     M& operator=(M& m) {  
  28.         cout<<"int M::operator=(M &a)"<<endl;  
  29.         return m;  
  30.     }  
  31.     virtual ~M() {  
  32.         cout<<"int M::~M()"<<endl;  
  33.     }  
  34. };  
  35. class B:virtualpublic M {  
  36. public:  
  37.     B() {  
  38.         cout<<"int B::B()"<<endl;  
  39.     }  
  40.     B(B &a) {  
  41.         cout<<"int B::B(B &a)"<<endl;  
  42.     }  
  43.     B& operator=(B& b) {  
  44.         cout<<"int B::operator=(B &a)"<<endl;  
  45.         return b;  
  46.     }  
  47.     virtual ~B() {  
  48.         cout<<"int B::~B()"<<endl;  
  49.     }  
  50. };  
  51. class N :public A {  
  52. public:  
  53.     N() {  
  54.         cout<<"int N::N()"<<endl;  
  55.     }  
  56.     N(N &a) {  
  57.         cout<<"int N::N(N &a)"<<endl;  
  58.     }  
  59.     N& operator=(N& n) {  
  60.         cout<<"int N::operator=(N &a)"<<endl;  
  61.         return n;  
  62.     }  
  63.     virtual ~N() {  
  64.         cout<<"int N::~N()"<<endl;  
  65.     }  
  66. };  
  67. class C:virtualpublic N {  
  68. public:  
  69.     C() {  
  70.         cout<<"int C::C()"<<endl;  
  71.     }  
  72.     C(C &a) {  
  73.         cout<<"int C::C(C &a)"<<endl;  
  74.     }  
  75.     C& operator=(C& c) {  
  76.         cout<<"int C::operator=(C &a)"<<endl;  
  77.         return c;  
  78.     }  
  79.     virtual ~C() {  
  80.         cout<<"int C::~C()"<<endl;  
  81.     }  
  82. };  
  83. class E:virtualpublic M{  
  84. public:  
  85.     E() {  
  86.         cout<<"int E::E()"<<endl;  
  87.     }  
  88.     E(E &a) {  
  89.         cout<<"int E::E(E &a)"<<endl;  
  90.     }  
  91.     E& operator=(E& e) {  
  92. 相關推薦

    C++在繼承繼承繼承建構函式複製建構函式操作符函式執行順序執行內容

    一、本文目的與說明     1. 本文目的:理清在各種繼承時,建構函式、複製建構函式、賦值操作符、解構函式的執行順序和執行內容。     2. 說明:雖然複製建構函式屬於建構函式的一種,有共同的地方,但是也具有一定的特殊性,所以在總結它的性質時將它單獨列出來了。  

    1C++】類&物件/建構函式/拷貝建構函式/操作符過載/函式

    一、C++類 & 物件     C++ 在 C 語言的基礎上增加了面向物件程式設計,C++ 支援面向物件程式設計。類是 C++ 的核心特性,通常被稱為使用者定義的型別。     類用於指定物件的形式,它包含了資料表示法和用於處理資料的方法。類中的資料和方法稱為類的成員。函式在

    C++:面試應該實現的string類(建構函式拷貝建構函式運算子過載和解函式

    一、string類的4個基本函式是什麼? 建構函式 拷貝建構函式 賦值運算子過載 解構函式 二、函式實現 1.建構函式 String(char* pStr = " ")

    操作符比較操作符算術操作符邏輯操作符位域操作符

    include pause 比較操作符 int clu put nbsp pan code 賦值操作符、比較操作符、算術操作符、邏輯操作符、位域操作符 , 如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“&l

    C++繼承成員函式(包括函式複製建構函式)學習筆記

    通過哺乳類派生貓、狗等學習繼承、多型中的知識點 先貼上類的程式碼 #include<iostream> enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERAMN, LAB };//犬種 class Mam

    C++類中的一些細節(過載重寫覆蓋隱藏建構函式函式拷貝建構函式函式繼承的一些問題)

    1 函式的過載、重寫(重定義)、函式覆蓋及隱藏 其實函式過載與函式重寫、函式覆蓋和函式隱藏不是一個層面上的概念。前者是同一個類內,或者同一個函式作用域內,同名不同引數列表的函式之間的關係。而後三者是基類和派生類函式不同情況下的關係。 1.1 函式過載

    從零開始學C++之虛擬函式型(一):虛擬函式表指標函式object slicing與虛擬函式C++物件模型圖

    #include <iostream>using namespace std;class CObject {public:     virtual void Serialize()     {         cout << "CObject::Serialize ..." <&

    C++之繼承(多重繼承+繼承+繼承+函式+重定義)

    多重繼承和多繼承 這個我們來講講這兩個的概念問題,一字之差,千差萬別。 多重繼承,比如有三個類,人類-士兵類-步兵類,三個依次繼承,這樣的繼承稱為多重繼承。 class Person {}; class Soldier :public Person

    C++中抽象類以及/純函式的區別與介紹

    一、虛擬函式 在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,用法格式為:virtual+函式返回型別+ 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。 二、純虛擬函式 純虛擬函式是一種

    Swift -繼承屬性重寫父類懶載入函式

    1. 新建工程命名:zhoukaojineng,建立一個類Person,在類中定義方法eat,實現列印“吃飯” 2. 建立一個繼承自Person的Teacher類,在Teacher類中定義方法teach,實現列印“上課”,呼叫其父類的eat函式 3. 建立一個類Student並繼承與Pe

    [收集]c++抽象類純虛擬函式以及巧用純函式實現介面類

    在Java、C#中有關鍵詞abstract指明抽象函式、抽象類,但是在C++中沒有這個關鍵詞,很顯然,在C++也會需要只需要在基類宣告某函式的情況,而不需要寫具體的實現,那C++中是如何實現這一功能的,答案是純虛擬函式。 含有純虛擬函式的類是抽象類,不能生成物件,只能派生。

    【小家java】類中靜態程式碼塊構造程式碼塊靜態變數執行順序繼承邏輯

    相關閱讀 每篇一句 上帝給每個人都安排了幸福的一生,我們的任務就是把它走完 1、概述 誠如各位所知,java的三大特性:封裝、繼承、多型。其中繼承,是java中最有學問的一點也是最相對來說最難理解的一些東西,本文針對於此,做一些例項分析,希望能夠幫助大家

    C++學習:虛擬函式,純虛擬函式(virtual),繼承,函式

    C++學習:虛擬函式,虛繼承,純虛擬函式(virtual)虛解構函式 虛擬函式 純虛擬函式 虛解構函式 虛繼承 簡介 在java這種高階語言中,有abstract和interface這兩個關鍵字.代表的是抽象類和介面,但是在C++這門語言中

    c++類的拷貝與銷毀(拷貝構造函數拷貝運算符析函數)

    錯誤 保存 編譯 oid 生成 標準庫 int 為什麽 explicit 拷貝構造函數 如果一個構造函數的第一個參數是自身類類型的引用,且任何額外參數都有默認值,則此構造函數是拷貝構造函數。 拷貝構造函數第一個參數必須是一個引用類型。此參數幾乎總是一個con

    繼承裡既有繼承也有虛擬函式繼承(即既有基表也有虛擬函式表)

    對於單一的虛繼承可參考這篇部落格: https://blog.csdn.net/sophia__yu/article/details/82791522 對於有虛擬函式繼承可參考這篇部落格: https://blog.csdn.net/sophia__yu/article/details/82

    C++筆記】編寫類string的建構函式函式函式

    #include<iostream> using namespace std; class String { public: String(const char *str=NULL); //普通建構函式 String(const Stri

    函式虛擬函式考題

    虛解構函式、虛擬函式結合考題變種 1.[Effective C++原則07]:為多型基類宣告virtual 解構函式。 [如果不]: 如果不宣告為解構函式,可能出現的結果如下:Derived物件的成分沒有被銷燬,形成資源洩露、在除錯上會浪費很長時間。 #incl

    繼承派生類建構函式函式順序

    派生類建構函式形式: 派生類建構函式 (引數表):基類建構函式(引數表) 類物件成員1(引數表)... 類物件成員n(引數表)//只能用表示式的方式對類物件成員進行初始化 {...派生類自定義的資料成員初始化} 在派生類中,首先呼叫基類的建構函式,其次呼叫

    C++中class類 的 建構函式函式

    說明: demo.cpp:main.cpp所在之處 Line.h:線段類的.h檔案 Line.cpp:線段類的.cpp檔案 Coordinate.h:座標類的.h檔案 Coordinate.cpp:

    c++單鏈表【建構函式運算子過載函式增刪查改等】

    c++中的單向連結串列寫法:實現增刪查改、建構函式、運算子過載、解構函式等。建立標頭檔案SList.h#pragma once typedef int DataType; //SList要訪問SListNode,可以通過友元函式實現,友元函式在被訪問的類中 class SL