對C++多態的一次小分析

來源:互聯網
上載者:User

標籤:不同   內容   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++多態的一次小分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.