多態現在一般的用法,就是拿一個父類的指針去調用子類中被重寫的方法。但我搞不懂爲什麼要那麼做,我們直接在子類中寫一個同名的成員函數,從而隱藏父類的函數不就行了麼?
將父類比喻爲電腦的外設接口,子類比喻爲外設,現在我有移動硬盤、U盤以及MP3,它們3個都是可以作爲存儲但是也各不相同。如果我在寫驅動的時候,我用個父類表示外設接口,然後在子類中重寫父類那個讀取設備的虛函數,那這樣電腦的外設接口只需要一個。但如果我不是這樣做,而是用每個子類表示一個外設接口,那麼我的電腦就必須有3個接口分別來讀取移動硬盤、U盤以及MP3。若以後我還有SD卡讀卡器,那我豈不是要將電腦拆了,焊個SD卡讀卡器的接口上去?
所以,用父類的指針指向子類,是爲了面向接口編程。大家都遵循這個接口,弄成一樣的,到哪裏都可以用,準確說就是“一個接口,多種實現“。
這個解答很有意思!!!
忽然感覺學了很長時間的C++只是知道有多態這樣一個重要的概念,編程時怎麼用,卻真的沒有仔細想一下它的機制,也就是它是怎麼實現的。於是看了些資料,做一下總結。
先看一個例子:
執行完之後的結果是這樣的:
這個很好理解,但當我們將函數g()加上virtual之後再看結果會看到
變成了4。這是因爲在後者中變成了虛函數了。
virtual是讓子類與父類之間的同名函數有聯繫,這就是多態性,實現動態綁定。
任何類若是有虛函數就會比比正常的類大一點,所有有virtual的類的對象裏面最頭上會自動加上一個隱藏的,不讓我知道的指針,它指向一張表,這張表叫做vtable,vtable裏是所有virtual函數的地址。
下邊來看這樣兩段代碼:
class Shape { public: Shape(); virtual ~Shape(); virtual void render(); void move(const pos&); virtual void resize(); protected: pos center; };
這個類的內存分佈是這樣的:
就是在成員變量前有一個vtable的指針,它會指向一個table,這個table叫做虛函數表。
class Ellipse : public Shape{ public: Ellipse (float majr, float minr); virtual void render(); protected: float major_axis; float minor_axis; };
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函數:
int main()
{
A a;
B b;
A *p = b;
p->f();
a = b;
a.f();
return 0
}
執行結果是:
B::f() 20
A::f() 10
可見a.f()的結果輸出是不同的。有很多理由說明這個,其一就是通過指針或引用纔是動態綁定,通過點運算是不可以的。
多態特性的工作依賴虛函數的定義,在需要解決多態問題的重載成員函數前,加上virtual關鍵字,那麼該成員函數就變成了虛函數,從上例代碼運行的結果看,系統成功的分辨出了對象的真實類型,成功的調用了各自的重載成員函數。
虛函數的定義要遵循以下重要規則:
1.如果虛函數在基類與派生類中出現,僅僅是名字相同,而形式參數不同,或者是返回類型不同,那麼即使加上了virtual關鍵字,也是不會進行滯後聯編的。
2.只有類的成員函數才能說明爲虛函數,因爲虛函數僅適合用與有繼承關係的類對象,所以普通函數不能說明爲虛函數。
3.靜態成員函數不能是虛函數,因爲靜態成員函數的特點是不受限制於某個對象。
4.內聯(inline)函數不能是虛函數,因爲內聯函數不能在運行中動態確定位置。即使虛函數在類的內部定義定義,但是在編譯的時候系統仍然將它看做是非內聯的。
5.構造函數不能是虛函數,因爲構造的時候,對象還是一片位定型的空間,只有構造完成後,對象纔是具體類的實例。
6.析構函數可以是虛函數,而且通常聲名爲虛函數。
同時需要了解多態的特性的virtual修飾,不單單對基類和派生類的普通成員 函數有必要,而且對於基類和派生類的析構函數同樣重要!!!