標籤:不同 內容 public tab 方法 body 地方 表數 技術分享
c++的多態性可以分為兩種:
1.編譯時間多態:運算子多載和函數重載。這個比較簡單,就簡單介紹一下,重點是運行時多態。
運算子多載主要運用c++的operator關鍵字對運算子重新定義:
class test{ int a,b;public: test(); test(int a,int b); void operator>>(test &);};test::test(){ a=b=1;}test::test(int a,int b){ this->a=a; this->b=b;}void test::operator >>(test &p){ cout<<p.a<<" "<<p.b<<endl;}int main(){ test cout(1,2),b; cout>>b; return 0;}
函數重載:有一點要記住:只有傳回值不同的重定義函數是錯誤的,其它的不多說了。
2.運行時多態:其一虛基類表可以說主要來自處理繼承的倒平行四邊形問題(套用別人的話。。。),即一個基類有多個子類繼承,而多個子類又只由一個孫子類繼承。如下面一個例子:
class base{public: int a;};class d1 :public base{public: int b;};class d2 :public base{public: int c;};class d3 :public d1, public d2{public: int sum;};int main(){ d3 _d3; _d3.a = 10; _d3.b = 20; _d3.c = 30; _d3.sum = _d3.a + _d3.b + _d3.c;//error cout << _d3.sum << endl;}
注釋error的地方就是因為_d3.a不明確,因為即可能是d1,d2的也可能是d3的,那麼要解決這個問題,有兩個方法:
第一個是標明範圍即_d3.d1::a,這個方法比較簡單並且基本也不用,不再贅述
第二個就是使用virtual關鍵字使多類繼承只保留一個基類的副本(重點來了!!)
class base{public: int a;};class d1 :virtual public base{public: int b;};class d2 :virtual public base{public: int c;};class d3 :public base{public: int d;};class d4 :public d1, public d2{public: int sum;};int main(){ d4 _d4; _d4.a = 10; _d4.b = 20; _d4.c = 30; _d4.sum = _d4.a + _d4.b + _d4.c; cout << _d4.sum << endl;//correct cout << sizeof(d1)<< " " << sizeof(d2) << " " << sizeof(d3) << endl;// 輸出結果12 12 8 return 0;}
通過在繼承的過程中加上virtual關鍵字,說明是一個虛基類,虛基類在發生多重繼承時,只會保留一個相同的類成員,從而從根本上解決了倒平行四邊形效應!
另外對最後一個輸出,可以看出來 沒有加virtual關鍵字的類比加了的少4個位元組。這是什麼原因呢,通過反組譯碼可以看一下:
可以看出每個類都有虛基類表作為對象的資料成員儲存,所以上面的程式執行後都會多出4位元組來。
其二虛函數(可以說是運行多態的主要內容了):虛函數 跟函數重載形式上很像,但完全不是一個東西,虛函數必須保證函數名、參數都完全相同,只是函數體不一樣而已。
虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的,即虛表。簡稱為V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的執行個體中這個表被分配在了這個執行個體的記憶體中,所以,當我們用父類的指標來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。
通過一個例子來分析一下:
class test{public: virtual void vfunc() { cout << "base‘s vfunc" << endl; }};class d1 :public test{public: void vfunc() { cout << "d1‘s vfunc!" << endl; }};class d2 :public test{public: void vfunc() { cout << "d2‘s vfunc!" << endl; }};int main(){ _asm { int 3 }//用於調試 test a, *p; d1 b; d2 c; p = &a; p->vfunc(); p = &b; p->vfunc(); p = &c; p->vfunc(); return 0;}
CPU Disasm地址 十六進位資料 指令 注釋0101273E CC int30101273F 8D4D F8 lea ecx, [ebp-8] ; this 指標 a01012742 E8 40E9FFFF call Demo.01011087 ; test::test01012747 8D4D E0 lea ecx, [ebp-20] ; this 指標 b0101274A E8 9FEBFFFF call Demo.010112EE ; d1::d10101274F 8D4D D4 lea ecx, [ebp-2C] ; this 指標 c01012752 E8 96ECFFFF call Demo.010113ED ; d2::d201012757 8D45 F8 lea eax, [ebp-8] ; 對象a的地址儲存到eax中0101275A 8945 EC mov dword ptr [ebp-14], eax ; 藉助對象指標p儲存對象a0101275D 8B45 EC mov eax, dword ptr [ebp-14] ; 設定當前this指標為對象a的01012760 8B10 mov edx, dword ptr [eax] ; 虛表’vftable01012762 8BF4 mov esi, esp01012764 8B4D EC mov ecx, dword ptr [ebp-14]01012767 8B02 mov eax, dword ptr [edx]01012769 FFD0 call eax
通過反組譯碼可以看出來,調用虛函數主要是通過this指標區分不同的函數,並且虛表在對象資料的首部。
下面來看一下vftable虛表資料結構:
簡單來說,繼承父類的子類中的虛表算是一個二維數組,盜用一下別人的圖,討論一下多重繼承:
三個父類派生出一個子類,其中子類的f()函數覆蓋了父類中的f()函數,來看一下子類中的虛表結構:
通過這張圖我們可以很清晰的看出來每一個父類都在佔有一個數組。對於這個程式再進行一次反組譯碼:
class Base1{public: virtual void vfunc() { cout << "base‘s vfunc" << endl; }};class Base2{public: virtual void vfunc() { cout << "d1‘s vfunc!" << endl; }};class Derive :public Base1,public Base2{public: void vfunc() { cout << "d2‘s vfunc!" << endl; }};int main(){ _asm { int 3 } Derive d; d.vfunc(); return 0;}
通過eax+4可以看出來這個線性結構的遞增,即虛表的增加。(這OD突然崩了。崩的莫名其妙。。。本來打算複製過來的,結果只能截屏了,也沒辦法注釋分析了。。。見諒。。)
對C++多態的一次小分析