c++中爲何能夠經過指針或引用實現多態,而不能夠經過對象呢?

引言:  在c++中司空見慣的事情就是:能夠經過指針和引用能夠實現多態,而對象不能夠。  那爲何?讓咱們來解開這神祕的暗紗!c++

 

一、 類對象的存儲方式:

在一個類的實例中,只會存放非靜態的成員變量。 若是該類中存在虛函數的話,再多加一個指向虛函數列表指針—vptr。函數

例如聲明以下兩個類,並分別實例化兩個對象,它們的內存分配大體以下:(vptr具體在什麼位置,與編譯器有關,大多數都在開始處)性能

class base
{
public:
    virtual ~base() {};
    virtual string GetName() { return "base"; }
    GetA();
    int a;
};

class derived : public base
{
public:
    virtual ~derived() {};
    virtual string GetName() { return "derived";}
    GetB();
    int b;
};

base B1, B2;
derived D1, D2;

 

內存分佈大體以下: spa

1. 類對象中,只有成員變量與vptr.指針

2. 普通成員函數在內存的某一位置放着。它們與c語言中定義的普通函數沒有區別。 當咱們經過對象或對象指針調用普通成員函數時, 編譯器會拿到它。怎麼拿到呢?固然是經過名字了,編譯器都會對咱們寫的函數的名字進行修飾映射,讓它們變成內存中惟一的函數名。code

3. 不管基類仍是子類,每一種類類型的虛函數表只有一份,它裏面存放了基類的類型信息和指向基類中的虛函數的指針。 某一類類型的全部對象都指向了相同的虛函數表。對象

 

 

2.  不管經過對象仍是指針,能使用的方法只與它們靜態類型有關。

例如:下面的 base類型的對象B1或指針pB1,只能使用GetName() 和GetA()方法。 不管它們是如何來的!!!!!blog

// 直接構造獲得
base B1;
base* pB1 = new base();

// 即便從子類轉換而來, 經過B1或pB1也永遠訪問不到GetB()方法。
derived d1;
B1 = d1;
pB1 = new derived();

 

 

3.  不一樣類型的指針有什麼區別?

本質上它們沒有任何區別,在32/64位系統中都是4/8字節的一個變量。 惟一不一樣的就是編譯器解釋它們的方式,即經過指針來尋址出來的對象類型不一樣,大小不一樣 ,指針類型來告訴編譯器如何解釋該指針。內存

 

4. 指針與引用來實現多態

有代碼以下 :編譯器

derived* _pD = new derived();
base* _pB = _pD;
_pB.GetName();    // 返回 derived.

想要知道如何經過指針來實現的多態,就要看看對基類指針賦值是發生了什麼! 具體來講 以下圖所示:

咱們會發現,對指針的賦值,僅僅是讓基類指針_pB指向的子類對象的地地址。 當咱們使用基類指針調用GetName()函數(該函數是虛函數,它的地址在函數表中)時, 會由_pB指向的地址找到子類的虛函數表指針vptr_上海,再由vptr_上海在虛函數表中找到子類的GetName(),從而調用它。就這樣實現了多態。 

 

5. 對象不能實現多態

有代碼以下:

base B1;
derived D1;
B1 = D1;
B1.GetName();     // 返回 base

base B2 = D1
B2.GetName();    // 返回 base

上面代碼中不管賦值操做仍是賦值構造時, 只會處理成員變量,一個類對象裏面的vptr永遠不會變,永遠都會指向所屬類型的虛函數表,操做以下圖所示:

 

所以,經過對象調用虛函數時,就沒有必要進行動態解析了,白白增長了間接性,浪費性能。編譯器直接在編譯時就能夠確認具體調用哪個函數了,所以沒有所謂的多態。

 

 

補充說明:

1. 引用本質上也是經過指針的解引用(即*_point)來實現的,能夠<<參考std源碼剖析》一本書,因此引用也能夠實現多態。

2. 即便經過 基類的指針調用基類的虛函數 或 經過子類的指針調用子類的虛函數 以及經過子類指針調用基類的虛函數,  也是經過多態機制來完成的(即一步步的間接性來完成)。 

3.  一個空的class的對象的大小爲1個字節, 編譯器之因此要這麼作,是爲了區別同一個類類型的不一樣對象!

相關文章
相關標籤/搜索