C++中有關多態和繼承的那些事

     今天就主要和大家分享下多態和繼承的那些事,我們先來看百度百科是如何對於多態和繼承下定義的。

    多態(Polymorphism)按字面的意思就是“多種狀態”。在面嚮對象語言中,接口的多種不同的實現方式即爲多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成爲一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作(摘自“Delphi4 編程技術內幕”)。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。多態性在Object Pascal和C++中都是通過虛函數(Virtual Function) 實現的。

    C++繼承:通過繼承機制,可以利用已有的數據類型來定義新的數據類型。所定義的新的數據類型不僅擁有新定義的成員,而且還同時擁有舊的成員。我們稱已存在的用來派生新類的類爲基類,又稱爲父類。由已存在的類派生出的新類稱爲派生類,又稱爲子類。

   上述則是百度百科對於繼承和派生的定義,爲了大家更好的理解,我下面舉個例子來更好的說明問題:

    首先我畫出了基本的類圖:

      這張圖大概就表示了一種通信方式就是繼承通信,從圖上可以直觀的看出Base爲基類,在基類裏面並沒有定義數據只有一個叫Do的方法,類B、類A、類C 都繼承於Base。它們在繼承的同時還有自己的方法就是do( ).

    接下來我們來看基類Base的具體實現:

//基類
class Base{
    public:
        virtual void Do() = 0;    //純虛函數
        virtual ~Base(){
            cout<<"Base des"<<endl;
        }
        
};

 在基類中我們並沒有封裝數據,只有一個純虛函數Do提供 一個接口,還有一個就是析構函數。

  下來我們來看其他類的實現:

//類A
class A :public Base{
    public:
        void Do(){
            cout<<"A's Do"<<endl;
        }
        ~A(){
            cout<<"A des"<<endl;
        }
};
//類B
class B :public Base{
    public:
        void Do(){
            cout<<"B's Do"<<endl;
        }
        ~B(){
            cout<<"B des"<<endl;
        }
};
//類C
class C :public Base{
    public:
        void Do(){
            cout<<"C's Do"<<endl;
        }
        ~C(){
            cout<<"C des"<<endl;
        }
};

//類My
class My{
    private:
        Base * pb;
    public:
        My(Base *t){
            pb = t;
        }
        void Do(){
            pb ->Do();    //多態
        }
        ~My(){
            
            cout<<"My des"<<endl;
            delete pb;
        }
};

客戶端:

//客戶端
//
//                +---------+      +--------+
//    my -------->|  pb     |----->|        |
//                +---------+      +--------+
//                                     (B)
int main(int argc,char **argv)
{
    My *my = new My(new B());
    my->Do();
    delete my;
    return 0;
}

首先來看一下執行的結果:

    下面給大家分析爲什麼會出現這個結果:

           我們來看客戶端的程序:

               (1)首先用new創建類My的對象,在類My的實現中,把基類Base指針作爲它的數據成員,那麼在類My的構造函數中就要初始化pb指針,我們可以看到在類My的構造函數中,對pb進行了初始化,所以在客戶端創鍵類My的對象時,就要傳遞一個基類Base的指針,我們看到在創建時仍用new創建了類B的對象,所以創建類My的對象時,就會調用類My的構造,將會把一個類B的指針賦給基類,這樣就使得基類指針指向了派生類B。

                (2)然回用對象my去調自己的方法Do(),因爲pb是個基類指針指向了派生類B,所以pb->Do( )就會調用類B的Do( )方法,所以就會在屏幕上打印B's Do

                 (3)在客戶端也給大家畫出了內存圖,當執行完程序準備釋放時,我們必須先釋放pb所指向的單元,然後在釋放my指針,

當客戶端發出delete my指令時,程序首先要調用類My的析構函數,所以纔會打印出My des,打印完之後,在析構函數裏又執行了delete pb 指令,pb是一個指向類B 的基類指針,由於在基類Base中把析構函數也設置爲了虛函數,由於基類指針pb無法完全映射派生類B的全部內存(因爲派生類不僅從基類繼承,還可以由自己的成員和方法,在內存中的低地址爲基類成員,然後高地址是自己類的,也就是說派生類中的繼承順序是先基類然後是自己),所以析構時要先釋放派生類,再釋放基類,所以屏幕上就會先出現

B des,然後出現Base des。

      我們再思考一下,如果將基類Base中析構函數中的virtual關鍵字去掉之後,程序會發生什麼呢??

     上圖就是,去掉基類Base的析構函數的virtual,即就是基類的析構函數不具有虛性質,我們來分析一下,當客戶端發出delete my的指令時,就會調用類My的析構函數,所以屏幕上還會打印出My des,打印完之後就遇到了delete pb,pb是一個指向派生類的基類的指針,在內存中只會映射基類的那部分,映射不到自己的析構函數,所以也就只調用了基類的析構函數,這是存在內存泄露的!!!

  下面給大家說明基類和派生類之間的數據訪問:

     

#include <iostream>
using namespace std;

class Base{
    private:
        int data;
        Base * next;
    public:
        Base(Base *t){
            data = 0;
            next = t;
        }
        Base(int data,Base *t){
            this->data = data;
            next = t;
        }
        virtual void fun(){
            cout<<"data = "<<data<<endl;
            if(next){
                next->fun();
            }
        }
        virtual ~Base(){
            cout<<"Base des"<<endl;
        }
};

class D1:public Base{
    public:
        D1(Base *t):Base(t){}
        D1(int data,Base*t):Base(data,t){}

        ~D1(){
            cout<<"D1 des"<<endl;
        }
};

class D2:public Base{
    public:
        D2(Base *t):Base(t){}
        D2(int data,Base*t):Base(data,t){}

        ~D2(){
            cout<<"D2 des"<<endl;
        }
};

class D3:public Base{
    public:
        D3(Base *t):Base(t){}
        D3(int data,Base*t):Base(data,t){}

        ~D3(){
            cout<<"D3 des"<<endl;
        }
};

int main(int argc,char ** argv)
{
    D3 * pd3 = new D3(5,NULL);
    D2 * pd2 = new D2(8,pd3);
    Base * pb = new D1(11,pd2);
    pb->fun();
    delete pb;
    delete pd2;
    delete pd3;
    return 0;
}
 我們先來看其執行結果:

     

     客戶端的實現的功能就是一次調用將所有派生類的數據全部打印出來。

     我們首先來看整個程序的調用過程:

        (1)客戶端首先創建類D3的對象,我們都知道創建對象就要調構造函數,D3是繼承於基類Base,那麼調構造時先要調用基類的構造,由於客戶端在創建對象時傳入的是兩個參數,所以會調用D3的第二個構造函數,由於D3是繼承來的,所以會先去調用基類Base的構造函數,調用完之後pd3的data = 5,next = NULL,同理創建對象pd2和pb,

    

   創建完成就會形成一個鏈表。

     (2)程序執行pb ->fun( );由於pb是基類Base指針指向派生類D1,根據多態性就會調用類D1的fun(),但是類D1中沒有定義fun()的方法,所以就會調用基類的fun()方法,在基類的fun()方法中就會先打印出D1的data,所以屏幕上就會出現11,然後判斷D1的next是否爲空,next是一個基類指針,同樣利用多態,因爲D1的next中保存的是派生類D2的指針,所以next ->fun( ),就會調用類D2的fun()函數,同樣因爲類D2沒有fun( )函數,所以又會調用基類的fun()函數,在屏幕上打印8,因爲類D2的next保存着類D3的指針,接着又會調到類D3中去找fun()函數,同樣因爲類D3中沒有,就會到基類中執行fun()函數,執行完之後,就該返回到類D3中了,因爲D3的next爲空,從D3中又返回到基類中,再從基類中返回到D2中,又返回到基類中,最後再返回到客戶端。

       下面用圖示來說明:


    我們再思考一下,如果將基類的析構函數的虛性質去掉之後,程序會怎樣呢??

 

   和之前的分析一樣,會導致內存泄露。

    在繼承中對於基類的數據我們應該是私有化好呢還是受保護好呢??對於這個問題沒有哪個好,哪個不好,這要根據具體的需求而言,如果在派生類中對於基類的成員的操作都一樣時,這時我們可以將這些操作封裝到基類裏,私有化比較好,就比如上述的例子,但是對於派生類來說,每一個類對於基類的成員操作都不同時,這時就要設置成受保護類型的,在每個派生類裏單獨進行實現,因爲在共有繼承中,派生內可以繼承私有的數據但是無法訪問訪問基類中私有的數據,所以要設置成受保護類型,這樣在派生類才能訪問。

發佈了65 篇原創文章 · 獲贊 19 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章