多態是指使用相同的函數名來訪問函數不同的實現方法,可以簡單概括為“一種接口,多種方法”。
最常見的用法是:父類(基類)指針,基類指針可以指向任何子類(派生類)對象(實例),然後通過基類的指針調用實際派生類的成員函數,示例如下:
#includeusing namespace std; class Base { public: void f(int x){ cout << Base::f(int) << x << endl; } void f(float x){ cout << Base::f(float) << x << endl; } // 必須有virtual關鍵字,此為虛函數 virtual void g(void){ cout << Base::g(void) << endl;} }; class Derive: public Base { public: // virtual關鍵字,可有可無,此為虛函數 virtual void g(void){ cout << Derived::g(void) << endl;} }; int main(void) { Derive d; Base *pb = &d; pb->f(42); // 運行結果: Base::f(int) 42 pb->f(3.14f); // 運行結果: Base::f(float) 3.14 pb->g(); // 運行結果: Derived::g(void) return 0; }
C++ 中的虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的,簡稱為V-Table。每個含有虛函數的類有一張虛函數表,表中每一項是一個虛函數的地址, 也就是說,虛函數表的每一項是一個虛函數的指針。 沒有虛函數的C++類,是不會有虛函數表的。
下面通過一些示例簡單介紹虛函數表:
#includeusing namespace std; class Base { public: virtual void f() { cout << Base::f << endl; } virtual void g() { cout << Base::g << endl; } virtual void h() { cout << Base::h << endl; } }; int main(void) { Base b; b.f(); //Base::f b.g(); //Base::g b.h(); //Base::h return 0; }
注意:在上面這個圖中,虛函數表的最後多加了一個結點,這是虛函數表的結束結點,就像字符串的結束符“”一樣,其標志了虛函數表的結束。這個結束標志的值在不同的編譯器下是不同的。
下面,再讓我們來看看繼承時的虛函數表是什麼樣的。
假設有如下所示的一個繼承關系:
#includeusing namespace std; class Base { public: virtual void f() { cout << Base::f << endl; } virtual void g() { cout << Base::g << endl; } virtual void h() { cout << Base::h << endl; } }; class Derive:public Base { public: virtual void f1() { cout << Derive::f1 << endl; } virtual void g1() { cout << Derive::g1 << endl; } virtual void h1() { cout << Derive::h1 << endl; } }; int main(void) { Derive d; //派生類對象 return 0; }
無虛函數覆蓋的虛函數表特點如下:
1)虛函數按照其聲明順序放於表中。
2)父類(派生類)的虛函數在子類(基類)的虛函數前面。
沒有覆蓋父類的虛函數是毫無意義的。之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。在比較之下,我們可以更加清楚地知道其內部的具體實現。
下面,我們來看一下,如果子類中有重載了父類的虛函數,會是一個什麼樣子?
#includeusing namespace std; class Base { public: virtual void f() { cout << Base::f << endl; } virtual void g() { cout << Base::g << endl; } virtual void h() { cout << Base::h << endl; } }; class Derive:public Base { public: //重寫父類的f()虛函數 virtual void f() { cout << Derive::f << endl; } virtual void g1() { cout << Derive::g1 << endl; } virtual void h1() { cout << Derive::h1 << endl; } }; int main(void) { Base *p = NULL; //父類指針 Base b; //父類對象 Derive d; //子類對象 p = &b; //父類指針指向父類對象 p->f(); //運行結果:Base::f p = &d; //父類指針指向子類對象 p->f(); //運行結果:Derive::f return 0; }
有虛函數覆蓋的虛函數表特點如下:
1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。
2)沒有被覆蓋的函數依舊。
本例中:
Base *p = NULL; //父類指針
p = &d; //父類指針指向子類對象
p->f(); //運行結果:Derive::f
由 p(即 &d)所指的內存中的父類虛函數表的 f() 的位置已經被實際子類Derive::f()函數地址所取代,於是在實際調用發生時,是 Derive::f() 被調用了。這就實現了多態。