C++的繼承與多態

◆ 概念介紹ios

繼承:爲了代碼的重用,保留基類的本來結構,並新增派生類的部分,同時可能覆蓋(overide)基類的某些成員。ide

多態:一種將不一樣的特殊行爲和單個泛化記號相關聯的能力,分爲靜態多態和動態多態。函數

◆ 繼承:spa

一個派生類能夠經過繼承得到基類的全部成員,而無需再次定義它們。分爲publicprotectedprivate三種繼承方式,前兩種方式保持基類的全部成員的屬性不變,且派生類能夠訪問基類的publicprotected成員,但仍然不能訪問基類的private成員;private繼承將使得基類的全部成員在派生類中表現爲private屬性。3d

聲明一個派生類對象,即在構造派生類對象時,遵循基類的接口,構造基類子對象,構造派生類增長的部分。其中的組成由下圖所示:指針

當出現菱形繼承時,例以下圖所示:code

要構造一個SleepSofa對象,就要構造一個Sofa和一個Bed子對象,這其中又同時構造了兩次Furniture對象,這是不合理的。所以Bed和Sofa類要對Furniture類進行虛繼承(virtual public Furniture)來避免這種情況。orm

 

◆ 多態:對象

靜態多態:在編譯時期就已經肯定了的行爲,例如帶變量的宏,模板,函數重載,運算符重載,拷貝構造等。blog

動態多態:在運行時期才能肯定調用的行爲。例如虛函數調用機制。本部分主要討論的是動態多態。虛函數是實現動態多態的機制,其核心理念就是經過基類指針來訪問派生類定義的成員。成員函數在基類爲虛函數時,在派生類一樣也是虛函數。純虛函數是指不但願基類對象調用的成員函數,須要派生類覆蓋實現這樣的純虛函數。(注:若是某個成員函數在基類中沒有用virtual關鍵字修飾,即普通函數,而在派生類中卻又有徹底相同的成員函數聲明,兩個函數即便有相同的名字和相同的參數類型與數量,這兩個函數也是徹底不一樣的函數,由於類的做用域不一樣)

虛函數表(vtable):每一個類都擁有一個虛函數表,虛函數表中羅列了該類中全部虛函數的地址,排列順序按聲明順序排列,例如這樣兩個類

class Base
{
    virtual void f() {}
    virtual void g() {}
    //其餘成員
};
Base b;

class Derive : public Base
{
    void f() {}
    virtual void d() {}
    //其餘成員
};
Derive d;

 

虛表指針(vptr):每一個類有一個虛表指針,當利用一個基類的指針綁定基類或者派生類對象時,程序運行時調用某個虛函數成員,會根據對象的類型去初始化虛指針,從而虛表指針會從正確的虛函數表中尋找對應的函數進行動態綁定,所以能夠達到從基類指針調用派生類成員的效果。

那麼爲何須要虛指針和虛函數表來實現動態多態呢?由於不管是什麼函數,包括類內的虛函數和非虛函數,都會儲存在內存中的代碼段。可是當編譯器在編譯時,就能夠肯定普通函數和非虛函數的入口地址,以及其調用的信息,因此這指的是常量指針。當遇到動態多態時,虛函數真正的入口地址的指針要在運行時根據對象的類型才能肯定,因此要經過虛指針從虛函數表中找虛函數對應的入口地址。

固然,用基類指針綁定的子類對象,只能經過這個基類指針調用基類中的成員,由於做用域僅限於基類的子對象,子類新增的部分是看不見的。

總結爲下面這個例程:

#include <iostream>

using std::cout;
using std::endl;

class Base
{
    public:
        void fun() { cout << "Base::fun()" << endl; }
        virtual void vfun() { cout << "Base::virtual fun()" << endl; }
};

class Derive : public Base
{
    public:
        void fun() { cout << "Derive::fun()" << endl; }
        virtual void vfun() { cout << "Derive::virtual fun()" << endl; }
        void dfun() { cout << "Derive::dfun()" << endl; }
};


int main()
{
    Base* bp = new Base();
    Base* dp = new Derive();
    
    bp->fun();
    bp->vfun();
    
    dp->fun();
    dp->vfun();
    //dp->dfun(); //編譯錯誤:基類指針指向子類中基類的子對象
                  //不能看到子類的成員 
    
    delete bp;
    delete dp;
    
    
    return 0;
}

輸出爲:

能夠看出,bp綁定一個基類對象,調用本身的成員無異議;dp綁定的是一個子類對象,所以調用fun()時,因爲dp是一個基類指針,做用域在於基類中,因此調用的是基類的fun(),而調用vfun()是經過動態綁定調用虛函數表中被子類覆蓋的Derive::vfun(),而若是要調用dfun()時則會出現編譯錯誤,由於子類獨有成員基類指針不可見。

注:在解有關動態多態的題時,只要把握住一點:這個指針指向的究竟是基類對象仍是子類對象,若是是基類對象,則調用基類的成員函數,若是是子類對象,則要考慮到這個虛成員函數是否被子類中的成員覆蓋掉,便是否產生了動態綁定。另外還有一點,從子類對象強制類型轉換爲基類對象是容許的,而相反地要從基類對象強制轉換成子類對象是錯誤的(編譯不經過)。

Base* dp1 = new Derive(); 
Derive* dp2 = (Derive*) dp1; //基類指針指向的是子類對象,能夠強制轉化爲子類指針

Base* bp1 = new Base();
Derive* bp2 = (Base*) bp1; //錯誤,[Error] invalid conversion from 'Base*' to 'Derive*' [-fpermissive]                           //基類指針指向的是基類對象,不能強制轉化爲子類指針
相關文章
相關標籤/搜索