C++之易混淆知識點四---虛函數與虛繼承


    C++面向對象中,虛函數與虛繼承是兩個完全不同的概念。

一、虛函數

      C++程序中只要類中含有虛擬函數,編譯程序都會為此類生成一個對應的虛擬函數跳轉表(vtbl),該虛擬函數跳轉表是一個又若干個虛擬函數體入口地址組成的一個線性表。派生類的虛擬函數跳轉表的前半部分由父類的vtbl得出,但是里面的內容不一定相同,后半部分則對應着自己新定義的虛擬函數。

class Employee { protected: char *Name; int Age; public: void changeAge(int newAge); virtual void retire();   //虛函數
        Employee( char *n, int a ); ~Employee(); }; class Manager: public Employee { int Level; public: void changeLevel( int l); void retire();    //情況一、子類覆寫了虛函數
         Manager( char *n, int a, int l ); ~Manager(); };

情況一、子類覆寫了父類的虛函數,則此時子類和父類的虛函數表分別為:

 

情況二、子類沒有覆寫父類的虛函數,則此時子類和父類的虛函數表分別為:

class Manager: public Employee { int Level; public: void changeLevel( int l); //void retire(); //情況二、子類沒有覆寫虛函數
         Manager( char *n, int a, int l ); ~Manager(); };

情況三、子類中自己的虛函數,則此時子類和父類的虛函數表分別為:

class Manager: public Employee { int Level; public:virtual void changeLevel(int l);   //情況三、子類有自己的虛擬函數
         void retire(); Manager( char *n, int a, int l ); ~Manager(); };

二、虛繼承

    虛繼承是為了解決多重繼承中的問題而出現的。要理解虛繼承的實現機制,首先看一般繼承:

class A { char k[3]; public: virtual void aa() { }; }; class B:public A { char j[3]; public: virtual void bb() { }; }; class C:public B { char i[3]; public: virtual void cc() { }; }; int main() { cout<<sizeof(A)<<endl; cout<<sizeof(B)<<endl; cout<<sizeof(C)<<endl; return 0; }

得到的答案是:8 12 16

分析如下:

1、  對於類A,有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量char k[3],則根據數據對齊原則,可以得到sizeof(A)的大小為8;

2、   對於類B,也有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量j[3],但是類B是繼承自類A的,是一般繼承,不是虛繼承,所以可以得到sizeof(B)的大小為:  4(指向虛函數的虛指針)+4(自己的數據成員)+4(父類A的數據成員)=12;

3、  對於類C,也有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量i[3],同樣的,類C是繼承自類B的,是一般繼承,不是虛繼承,所以可以得到sizeof(C)的大小為4(指向虛函數的虛指針)+4(自己的數據成員)+8(父類的數據成員)=16;

   再來看一下虛繼承的情況:

class A { char k[3]; public: virtual void aa() { }; }; class B:public virtual A { char j[3]; public: virtual void bb() { }; }; class C:public virtual B { char i[3]; public: virtual void cc() { }; }; int main() { cout<<sizeof(A)<<endl; cout<<sizeof(B)<<endl; cout<<sizeof(C)<<endl; return 0; }

得到的答案是:8 20 32

分析如下:

1、對於類A,有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量k[3],則根據數據對齊原則,可以得到sizeof(A)的大小為8;

2、對於類B,也有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量j[3],但是類B是繼承自類A的,而且是虛繼承,虛繼承的實現是通過一個虛基類指針列表,比如vptr_B_A指向虛基類,同時還包含了父類的所有內容,所以可以得到sizeof(B)的大小為:4(指向自己虛函數的虛指針)+4(自己的數據成員)+4(指向虛基類的指針vptr_B_A)+8(父類A的內容大小)=20;

3、對於類C,也有一個虛函數,需要一個虛函數表vtbl來記錄函數的入口地址,每個地址一個虛指針,指針的大小為4,類中還有一個成員變量i[3],同樣的,類C是繼承自類B的,而且是虛繼承,虛繼承的實現是通過一個虛基類指針列表,比如vptr_C_B指向虛基類,同時還包含了父類的所有內容,所以可以得到sizeof(C)的大小為:4(指向自己虛函數的虛指針)+4(自己的數據成員)+4(指向虛基類的指針(vptr_C_B)+20(父類B的內容大小)=32;

class A { char k[3]; public: virtual void aa() { }; }; class B:public virtual A { char j[3]; public: virtual void bb() { }; }; class C:public virtual A { char i[3]; public: virtual void cc() { }; }; class D: public B,public C { char n[3]; public: virtual void dd() { }; }; int main() { cout<<sizeof(A)<<endl; cout<<sizeof(B)<<endl; cout<<sizeof(C)<<endl; cout<<sizeof(D)<<endl; return 0; }

得到的結果為:8 20 20 36

分析如下:

1、類sizeof(A,B,C),從前面的分析可以得到結果為8 20 20;

2、sizeof(D)=4(指向自己虛函數的虛指針)+4(自己的數據成員)+

                    12(父類B的內容,包括數據成員、指向虛函數的虛指針、以及虛基類指針列表vptr_B_A)+

                    12(父類C的內容,包括數據成員、指向虛函數的虛指針、以及虛基類指針列表vptr_C_A)+

                    4(類A的數據成員)=36。

     可見,類D中只包含了類A的一份副本,虛繼承很好地解決了多重繼承中的二義性問題。


免責聲明!

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



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