多態--虛函數表


 

多態現在一般的用法,就是拿一個父類的指針去調用子類中被重寫的方法。但我搞不懂為什么要那么做,我們直接在子類中寫一個同名的成員函數,從而隱藏父類的函數不就行了么?

然后有人這樣回答:

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

所以,用父類的指針指向子類,是為了面向接口編程。大家都遵循這個接口,弄成一樣的,到哪里都可以用,准確說就是“一個接口,多種實現“。

 

 

執行完之后的結果是這樣的:

這個很好理解,但當我們將函數g()加上virtual之后再看結果會看到

變成了4。這是因為在后者中變成了虛函數了。

virtual是讓子類與父類之間的同名函數有聯系,這就是多態性,實現動態綁定。

任何類若是有虛函數就會比比正常的類大一點,所有有virtual的類的對象里面最頭上會自動加上一個隱藏的,不讓我知道的指針,它指向一張表,這張表叫做vtable,vtable里是所有virtual函數的地址。

 

下邊來看這樣兩段代碼:

復制代碼
 1 class Shape {
 2  public:
 3      Shape();
 4      virtual  ~Shape();
 5      virtual void render();
 6      void move(const pos&);
 7      virtual void resize();
 8 protected:
 9      pos center;
10 };
復制代碼

 

這個類的內存分布是這樣的:

就是在成員變量前有一個vtable的指針,它會指向一個table,這個table叫做虛函數表。

復制代碼
 1 class Ellipse : public Shape{
 2 public:
 3     Ellipse (float majr, float minr);
 4     virtual void render();
 5 
 6 protected:
 7     float major_axis;
 8     float minor_axis;
 9 
10 };
復制代碼

 

Ellipse繼承與Shape,看一下它的內存分布:

這里的vtable不是對象的,而是屬於類的,這就是多態的實現機制。

如果需要了解更多關於虛表的知識,鏈接:

http://blog.csdn.net/haoel/article/details/1948051/

這樣由上面的解釋我們來詳細講解一下多態的概念和實現:

多態(Polymorphism)按字面的意思就是“多種狀態”。在面向對象語言中,接口的多種不同的實現方式即為多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。其實我看到過一句話:調用同名函數卻會因上下文的不同而有不同的實現。我覺得這樣更加貼切,還加入了多態三要素:(1)相同函數名  (2)依據上下文  (3)實現卻不同;

來看這個例子:

#include<iostream>
using namespace std;
class A {
public:
    A() : i(10){}
    virtual  void f() { cout<< "A::f()"<<i<<endl;}
 
    int i;
};
class B : public A{
public:
    B() : j(20) {}
    virtual void f()  { cout << "B::f()" << j <<endl;}
    int j;
};
int main()
{
   
A a;
B b;
A *p = &b;
p->f();
return 0;
}

 

這時我們執行這個程序,b的f()函數會執行,執行結果是"B::f() 20",這里就是多態中的動態綁定,本來是基類型的指針賦給了子類型的對象地址,這樣當運行時才能知道執行哪個f()函數。

之后修改main函數:

復制代碼
 1  int main()
 2  {    
 3  A a;
 4  B b;
 5  A *p = &b;
 6  p->f();
 7 a = b;
 8 a.f();
 9  return 0;
10 }
復制代碼

 

執行結果是:

B::f() 20

A::f() 10

可見a.f()的結果輸出是不同的。有很多理由說明這個,其一就是通過指針或引用才是動態綁定,通過點運算是不可以的。

多態特性的工作依賴虛函數的定義,在需要解決多態問題的重載成員函數前,加上virtual關鍵字,那么該成員函數就變成了虛函數,從上例代碼運行的結果看,系統成功的分辨出了對象的真實類型,成功的調用了各自的重載成員函數。

 

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

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

2.只有類的成員函數才能說明為虛函數,因為虛函數僅適合用與有繼承關系的類對象,所以普通函數不能說明為虛函數。 

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

4.內聯(inline)函數不能是虛函數,因為內聯函數不能在運行中動態確定位置。即使虛函數在類的內部定義定義,但是在編譯的時候系統仍然將它看做是非內聯的。 

5.構造函數不能是虛函數,因為構造的時候,對象還是一片位定型的空間,只有構造完成后,對象才是具體類的實例。 

6.析構函數可以是虛函數,而且通常聲名為虛函數。
 

同時需要了解多態的特性的virtual修飾,不單單對基類和派生類的普通成員 函數有必要,而且對於基類和派生類的析構函數同樣重要!!!

 

原文地址:https://www.cnblogs.com/dormant/p/5223215.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM