c++多態特性總結

 

 

將父類比喻爲電腦的外設接口,子類比喻爲外設,如今我有移動硬盤、U盤以及MP3,它們3個都是能夠做爲存儲可是也各不相同。若是我在寫驅動的時候,我用個父類表示外設接口,而後在子類中重寫父類那個讀取設備的虛函數,那這樣電腦的外設接口只須要一個。但若是我不是這樣作,而是用每一個子類表示一個外設接口,那麼個人電腦就必須有3個接口分別來讀取移動硬盤、U盤以及MP3。若之後我還有SD卡讀卡器,那我豈不是要將電腦拆了,焊個SD卡讀卡器的接口上去?html

因此,用父類的指針指向子類,是爲了面向接口編程。你們都遵循這個接口,弄成同樣的,到哪裏均可以用,準確說就是「一個接口,多種實現「。ios

對比於重載,派生類中的重載方法,在基類是不能調用的,編譯的時候就會出錯。編程

#include <iostream.h>  
class animal  
{  
public:  
    void sleep()  
    {  
        cout<<"animal sleep"<<endl;  
    }  
    virtual void breathe()  
    {  
        cout<<"animal breathe"<<endl;  
    }  
};  
  
class fish:public animal  
{  
public:  
    void breathe()  
    {  
        cout<<"fish bubble"<<endl;  
    }  
};  
void main()  
{  
    fish fh;  
    animal *pAn=&fh; // 隱式類型轉換  
    pAn->breathe();  
}  

 

結果是「fish bubble」數組

 編譯器在編譯的時候,發現animal類中有虛函數,此時編譯器會爲每一個包含虛函數的類建立一個虛表(即vtable),該表是一個一維數組,在這個數組中存放每一個虛函數的地址。函數

對於例1-2的程序,animal和fish類都包含了一個虛函數breathe(),所以編譯器會爲這兩個類都創建一個虛表,(即便子類裏面沒有virtual函數,可是其父類裏面有,因此子類中也有了)以下圖所示:ui

 

        那麼如何定位虛表呢?編譯器另外還爲每一個類的對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表。在程序運行時,根據對象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調用虛函數時,就可以找到正確的函數。對於例1-2的程序,因爲pAn實際指向的對象類型是fish,所以vptr指向的fish類的vtable,當調用pAn->breathe()時,根據虛表中的函數地址找到的就是fish類的breathe()函數。
       正是因爲每一個對象調用的虛函數都是經過虛表指針來索引的,也就決定了虛表指針的正確初始化是很是重要的。換句話說,在虛表指針沒有正確初始化以前,咱們不可以去調用虛函數。那麼虛表指針在何時,或者說在什麼地方初始化呢?
        答案是在構造函數中進行虛表的建立和虛表指針的初始化。還記得構造函數的調用順序嗎,在構造子類對象時,要先調用父類的構造函數,此時編譯器只「看到了」父類,並不知道後面是否還有繼承者,它初始化父類對象的虛表指針,該虛表指針指向父類的虛表。當執行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。對於例2-2的程序來講,當fish類的fh對象構造完畢後,其內部的虛表指針也就被初始化爲指向fish類的虛表。在類型轉換後,調用pAn->breathe(),因爲pAn實際指向的是fish類的對象,該對象內部的虛表指針指向的是fish類的虛表,所以最終調用的是fish類的breathe()函數。
要注意:對於虛函數調用來講,每個對象內部都有一個虛表指針,該虛表指針被初始化爲本類的虛表。因此在程序中,無論你的對象類型如何轉換,但該對象內部的虛表指針是固定的,因此呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。指針

總結(基類有虛函數):
1. 每個類都有虛表。
2. 虛表能夠繼承,若是子類沒有重寫虛函數,那麼子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。若是基類有3個虛函數,那麼基類的虛表中就有三項(虛函數地址),派生類也會有虛表,至少有三項,若是重寫了相應的虛函數,那麼虛表中的地址就會改變,指向自身的虛函數實現。若是派生類有本身的虛函數,那麼虛表中就會添加該項。
3. 派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。htm

        這就是C++中的多態性。當C++編譯器在編譯的時候,發現animal類的breathe()函數是虛函數,這個時候C++就會採用遲綁定(late binding)技術。也就是編譯時並不肯定具體調用的函數,而是在運行時,依據對象的類型(在程序中,咱們傳遞的fish類對象的地址)來確認調用的是哪個函數,這種能力就叫作C++的多態性。咱們沒有在breathe()函數前加virtual關鍵字時,C++編譯器在編譯時就肯定了哪一個函數被調用,這叫作早期綁定(early binding)。

C++的多態性是經過遲綁定技術來實現的。

C++的多態性用一句話歸納就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。若是對象類型是派生類,就調用派生類的函數;若是對象類型是基類,就調用基類的函數。對象

虛函數的定義要遵循如下重要規則: 

1.若是虛函數在基類與派生類中出現,僅僅是名字相同,而形式參數不一樣,或者是返回類型不一樣,那麼即便加上了virtual關鍵字,也是不會進行滯後聯編的。 

2.只有類的成員函數才能說明爲虛函數,由於虛函數僅適合用與有繼承關係的類對象,因此普通函數不能說明爲虛函數。 

3.靜態成員函數不能是虛函數,由於靜態成員函數的特色是不受限制於某個對象。 

4.內聯(inline)函數不能是虛函數,由於內聯函數不能在運行中動態肯定位置。即便虛函數在類的內部定義定義,可是在編譯的時候系統仍然將它看作是非內聯的。 

5.構造函數不能是虛函數,由於構造的時候,對象仍是一片未定型的空間,只有構造完成後,對象纔是具體類的實例。 

6.析構函數能夠是虛函數,並且一般聲名爲虛函數。析構函數爲何一般定義爲虛函數,是爲了在多態時,當基類指針操做派生類對象時,防止在析構時只析構基類而不析構派生類的狀況發生。
blog

相關文章
相關標籤/搜索