C++多态相关关问题及虚表剖析
關于C++多態的問題:(基于Visual Studio 2012編譯器)
?
一、多態引入
1、對象的類型:
(1)???靜態的類型:對象聲明時的類型,在編譯的時候確定
(2)???動態的類型:目前所指對象的類型,在程序運行時確定的
EG:
class Derived1:public Base
{};
?
class Derived2:public Base
{};
?
void FunTest()
{
???Derived1* pD1 = new Derived1;//對象pD1的靜態類型是Derived1, 動態類型是Derived1*
???Base* pB = pD1;//對象pB的靜態類型是Base*, 動態類型是Derived1*
???Derived2* pD2 = new Derived2;
???pB = pD2;//對象pB的靜態類型是Base*,動態類型是Derived2
}
?
2、多態:
(1)???多態的概念:一詞最初來源于希臘語,意思是具有多種形式或形態的情形,在C++中主要體現在想不通的對象發送同一個消息,不同的對象會產生不同的行為。
(2)???多態的類型:
A:一種為編譯時的多態,稱為靜態多態或靜態聯編。編譯時的多態性是指程序在編譯以前就確定的多態性,可以通過重載、泛型編程來實現。
B:另一種是運行時的多態,也稱為靜態聯編,運行時的多態是指在程序運行中才可以確定的多態性,是通過繼承和虛函數實現的。
(3)???動態的多態:
動態綁定(早綁定):在程序執行期間(非編譯期)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。
使用virtual關鍵字修飾類的成員函數時,指明該函數為虛函數,派生類需要重新實現,編譯器將實現動態綁定。
class CWashRoom
{
public:
???void GoToManWashRoom ()
??? {
???cout<< "Man--->Please Left" <<endl;
??? }
???void GoToWomanWashRoom ()
??? {
???cout<< "Woman--->Please Right" <<endl ;
??? }
};
class CPerson
{
public:
???virtual void GoToWashRoom(CWashRoom & _washRoom ) = 0;//virtual修飾函數為虛函數,在成員函數的形參后面寫上=0,則成員函數為純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。純虛函數在派生類中重新定義以后,派生類才能實例化出對象。
};
?
class CMan:public CPerson
{
public:
???virtual void GoToWashRoom(CWashRoom & _washRoom )//對基類函數的重寫,進行動態綁定,實現不同的功能。
??? {
???_washRoom.GoToManWashRoom();
??? }
};
class CWoman:public CPerson
{
public:
???virtual void GoToWashRoom(CWashRoom & _washRoom )
??? {
???_washRoom.GoToWomanWashRoom ();
??? }
};
?????????????????? 注:必須是虛函數,通過基類類型的引用或者指針調用虛函數
??????????????????????????? voidFunTest()
{
CWashRoom washRoom;
for ( int iIdx = 1 ; iIdx <= 10 ; ++iIdx)
{
CPerson* pPerson;
int iPerson = rand ()%iIdx;
if (iPerson&0x01)
{
pPerson = new CMan ;
}
else
{
pPerson = new CWoman ;
}
pPerson->GoToWashRoom (washRoom); //通過基類類型的指針調用在派生類中//進行重寫的虛函數,
delete pPerson;
pPerson = NULL ;
Sleep(1000 );
}
}
3、? ??繼承體系中同名成員函數的關系和區別
4、? 小結
1、派生類重寫基類的虛函數實現多態,要求函數名、參數列表、返回值完全相同。(協變除外)
2、基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性。
3、只有類的非靜態成員函數才能定義為虛函數,靜態成員函數不能定義為虛函數。
4、如果在類外定義虛函數,只能在聲明函數時加virtual關鍵字,定義時不用加。
5、構造函數不能定義為虛函數,雖然可以將operator=定義為虛函數,但最好不要這么做,使用時容易混淆
6、不要在構造函數和析構函數中調用虛函數,在構造函數和析構函數中,對象是不完整的,沒有傳遞this指針,可能會出現未定義的行為。
7、最好將基類的析構函數聲明為虛函數。(析構函數比較特殊,因為派生類的析構函數跟基類的析構函數名稱不一樣,但是構成覆蓋,這里編譯器做了特殊處理)
8、虛表是所有類對象實例共用的
?
?
5、? 虛表分析
(1)??????單繼承:
派生類中沒有重寫基類的虛函數
classBase
{
public:
??? Base()
??? {}
?
??? virtualvoid Test1()
??? {
??????? //data = 10;
??????? //cout<<this<<endl;
??????? //cout<<data<<endl;
??????? cout<<"BaseTest1()"<<endl;
??? }
?
??? virtualvoid Test2()
??? {
??????? cout<<"BaseTest2()"<<endl;
??? }
?
??? virtualvoid Test3()
??? {
??????? cout<<"BaseTest3()"<<endl;
??? }
?
??? int data;
};
?
classDerited:publicBase
{
public:
??? virtualvoid Test4()//復制一份基類虛函數,如果在派生類中對基類虛函數進行重寫,則用重寫的虛函數代替
??? {
??????? cout<<"DeritedTest4()"<<endl;
??? }
?
??? virtualvoid Test5()
??? {
??????? cout<<"DeritedTest5()"<<endl;
??? }
??? virtualvoid Test6()
??? {
??????? cout<<"Deritedtest6()"<<endl;
??? }
};
void FunTest2(Base& b)
{
??? b.Test2();
}
?
typedef void (*VFP)();
?
void printVpf()
{
??? Base b;
??? b.data1? = 1;
??? cout<<"Base::virtable of B:"<<endl;
??? VFP* vfp = (VFP *)*(int *)&b; //未傳遞this指針。
??????????????????????????????? ? //最好不要訪問類的成員變量
??? while(*vfp)
??? {
??????? (*vfp)();
??????? ++vfp;
??? }
??? cout<<endl;
?
??? Derited d;
??? d.data1 = 1;
??? d.data2 = 2;
??? cout<<"Base::virtable of D:"<<endl;
??? VFP* vfp1 = (VFP *)*(int *)&d;
?
??? while(*vfp1)
??? {
??????? (*vfp1)();
??????? ++vfp1;
??? }
??? cout<<endl;
}
在派生類中重寫基類的虛函數
?
classBase
{
public:
??? Base()
??? {}
?
??? virtualvoid Test1()
??? {
??????? //data = 10;
??????? //cout<<this<<endl;
??????? //cout<<data<<endl;
??????? cout<<"BaseTest1()"<<endl;
??? }
?
??? virtualvoid Test2()
??? {
??????? cout<<"BaseTest2()"<<endl;
??? }
?
??? virtualvoid Test3()
??? {
??????? cout<<"BaseTest3()"<<endl;
??? }
?
??? int data;
};
?
classDerited:publicBase
{
public:
??? virtualvoid Test1()//復制一份基類虛函數,如果在派生類中對基類虛函數進行重寫,則用重寫的虛函數代替
??? {
??????? cout<<"DeritedTest1()"<<endl;
??? }
?
??? virtualvoid Test3()
??? {
??????? cout<<"DeritedTest3()"<<endl;
??? }
??? virtualvoid Test4()
??? {
??????? cout<<"test4()"<<endl;
??? }
};
?
在重寫了基類虛函數的派生類虛表中先復制了一份基類的虛表,然后將在派生類中重寫的虛函數覆蓋掉基類的虛函數,派生類自己的虛函數跟在復制下來的虛表后邊。通過基類的指針或引用調用虛函數時調用的是被覆蓋之后的虛表里的虛函數。
?
(2)??????多繼承:
class Base1
{
public:
?
? virtual void Test1()
? {
????? cout<<"Base1Test1()"<<endl;
? }
?
? virtual void Test2()
? {
????? cout<<"Base1Test2()"<<endl;
? }
?
? virtual void Test3()
? {
????? cout<<"Base1Test3()"<<endl;
? }
?
? int data1;
};
?
class Base2
{
public:
? virtual void Test4()
? {
?
????? cout<<"Base2Test4()"<<endl;
? }
?
? virtual void Test5()
? {
????? cout<<"Base2 Test5()"<<endl;
? }
?
? virtual void Test6()
? {
????? cout<<"Base2Test6()"<<endl;
? }
?
? int data2;
};
?
class Derited:public Base1,public Base2
{
public:
? Derited()
? {
????? data3 = 3;
? }
? virtual void Test2()
? {
????? cout<<"DeritedTest2()"<<endl;
? }
?
? virtual void Test4()
? {
????? cout<<"DeritedTest4()"<<endl;
? }
? virtual void Test7()
? {
????? cout<<"DeritedTest7()"<<endl;
? }
? int data3;
} ;
?
typedef void (* VFP)();
?
void printVpf()
{
? Derited d;
? d.data1 = 1;
? d.data2 = 2;
? d.data3 = 3;
? Base1 &b1 = d;
? VFP* vfp = (VFP *)*(int *)&b1;
? cout<<"&b1 ="<<&b1<<endl;????????????????? ?
? while(*vfp)
? {
????? (*vfp)();
????? ++vfp;
? }
? cout<<endl;
?
? Base2 &b2 = d;
? cout<<"&b2 ="<<&b2<<endl;
? cout<<"&b2 ="<<(Base2 *)((int)&b1+sizeof(Base1))<<endl;
? VFP* vfp1 = (VFP *)*(int *)&b2;
?
? while(*vfp1)
? {
????? (*vfp1)();
????? ++vfp1;
? }
? cout<<endl;
??
?
?
?
?
?
在多繼承中,基類的虛函數如果在子類中重寫,則用基類的指針或引用調用虛函數,調用的是被覆蓋的虛表里的虛函數。
?
?
?
(3)??????菱形繼承
class Base
{
public:
? virtual void Test1()
? {
????? cout<<"Base::Test1()"<<endl;
? }
?
? int _data1;
};
class C1:public Base
{
public:
virtual void Test1()
? {
????? cout<<"C1::Test1()"<<endl;
? }
virtual void Test2()
? {
????? cout<<"C1::Test2()"<<endl;
? }
int _data2;
};
class C2:public Base
{
public:
virtual void Test1()
? {
????? cout<<"C2::Test1()"<<endl;
? }
virtual void Test3()
? {
????? cout<<"C2::Test3()"<<endl;
? }
int _data3;
};
class D:public C1,public C2
{
public:
virtual void Test1()
? {
????? cout<<"D::Test1()"<<endl;
? }
virtual void Test2()
? {
????? cout<<"D::Test2()"<<endl;
? }
virtual void Test3()
? {
????? cout<<"D::Test3()"<<endl;
? }
virtual void Test4()
? {
????? cout<<"D::Test4()"<<endl;
? }
int _data4;
};
typedef void(*VFP)();
void PrintVfp()
{
? D d;
? VFP* vfp1 = (VFP*)*(int *)&d;
? cout<<"D virtable:"<<endl;
? while(*vfp1)
? {
????? (*vfp1)();
????? ++vfp1;
? }
? cout<<endl;
?
? C2& c2 = d;
? VFP* vfp2 = (VFP*)*(int *)&c2;
? cout<<"C2 virtable:"<<endl;
? while(*vfp2)
? {
????? (*vfp2)();
????? ++vfp2;
? }
? cout<<endl;
?
? C1& c1 = d;
? VFP* vfp3 = (VFP*)*(int *)&c1;
? cout<<"C1 virtable:"<<endl;
? while(*vfp3)
? {
????? (*vfp3)();
????? ++vfp3;
? }
? cout<<endl;
Base& b = d;
}
int main()
{
? PrintVfp();
? D d;
? d.C1::_data1 = 0;
? d.C2::_data1 = 1;
? d._data2 = 2;
? d._data3 = 3;
? d._data4 = 4;
? system("pause");
? return 0;
}
?
在菱形繼承中C1的大小為C1的成員加上Base的成員大小再加上C1的虛表大小
D的大小為C1的大小加上C2的大小加上D自己的成員變量大小。在派生類中存儲了兩份Base類的對象,故訪問時存在二義性問題,由此引入了菱形虛擬繼承。
?
?
(4)??????菱形虛擬繼承:
class Base
{
public:
??? virtual void Test1()
??? {
??????? cout<<"Base::Test1()"<<endl;
??? }
?
??? int _data1;
???
};
class C1:virtual public Base
{
public:
??? virtual void Test1()
??????? {
??????????? cout<<"C1::Test1()"<<endl;
??????? }
??? virtual void Test2()
??????? {
??????????? cout<<"C1::Test3()"<<endl;
??????? }
?
?
int _data2;
};
class C2:virtual public Base
{
public:
??? virtual void Test1()
??????? {
??????????? cout<<"C2::Test2()"<<endl;
??????? }
??? virtual void Test3()
??????? {
??????????? cout<<"C2::Test4()"<<endl;
??????? }
??? int _data3;
};
class D:public C1,public C2
{
public:
??? virtual void Test1()
??????? {
??????????? cout<<"D::Test1()"<<endl;
??????? }
??? virtual void Test4()
??????? {
??????????? cout<<"D::Test4()"<<endl;
??????? }
??????? virtual void Test5()
??????? {
??????????? cout<<"DTest5()"<<endl;
??????? }
??? int _data4;
};
typedef void(*VFP)();
void PrintVfp()
{
??? D d;
??? VFP* vfp1 = (VFP*)*(int *)&d;
??? cout<<"D virtable:"<<endl;
??? while(*vfp1)
??? {
??????? (*vfp1)();
??????? ++vfp1;
??? }
??? cout<<endl;
?
??? C1 &c1 = d;
??? VFP* vfp2 = (VFP*)*(int *)&c1;
??? cout<<"C1 virtable:"<<endl;
??? while(*vfp2)
??? {
??????? (*vfp2)();
??????? ++vfp2;
??? }
??? cout<<endl;
?
??? Base& b = d;
??? VFP* vfp3 = (VFP*)*(int *)&b;
??? cout<<"Base virtable:"<<endl;
??? while(*vfp3)
??? {
??????? (*vfp3)();
??????? ++vfp3;
??? }
??? cout<<endl;
}
int main()
{?? cout<<sizeof(C1)<<endl;
??? cout<<sizeof(D)<<endl;
??? PrintVfp();
?
??? system("pause");
??? return 0;
}
?
C1的大小為20字節:基類Base成員data1大小+派生類C1成員data2大小+偏移指針+虛表指針
D的大小為36個字節大小:C1成員大小+C1虛表指針+C1偏移指針+C2成員大小+C2虛表指針+C2偏移指針+派生類D成員大小+基類Base虛表+Base成員
?
(5)??????派生類帶有構造函數+析構函數、構造函數、析構函數其中一個成員函數的虛表分析:
?
class Base
{
public:
??? Base()
??????? :data1(1)
??? {}
??? ~Base()
??? {}
??? virtual void Test1()
??? {
??????? cout<<"BaseTest1()"<<endl;
??? }
?
??? virtual void Test2()
??? {
??????? cout<<"BaseTest2()"<<endl;
??? }
?
??? virtual void Test3()
??? {
??????? cout<<"BaseTest3()"<<endl;
??? }
?
??? int data1;
};
class Derited:virtual public Base
{
public:
??? Derited()
??? {}
??? ~Derited()
??? {}
??? virtual void Test1()
??? {
??????? cout<<"DeritedTest1()"<<endl;
??? }
??? virtual void Test3()
??? {
??????? cout<<"Deritedtest3()"<<endl;
??? }
??? virtual void Test4()
??? {
??????? cout<<"DeritedTest4()"<<endl;
??? }
??? int data2;
};
typedef void (*VFP)()
void printVpf()
{
??? Derited d;
??? d.data1 = 1;
??? d.data2 = 2;
??? cout<<"vir table ofD:"<<endl;
??? VFP* vfp1 = (VFP *)*(int *)&d;
?
??? while(*vfp1)
??? {
??????? (*vfp1)();
??????? ++vfp1;
??? }
??? cout<<endl;
??? Base& b = d;
??? cout<<"vir table ofB:"<<endl;
??? VFP* vfp2 = (VFP *)*(int *)&b;
??? while(*vfp2)
??? {
??????? (*vfp2)();
??????? ++vfp2;
??? }
??? cout<<endl;
}
int main()
{
??? printVpf();
??? system("pause");
??? return 0;
}
?
?
?
?
?
構造函數在這個地方的作用:
(1)??????偏移量表地址的填寫
(2)??????調用基類構造函數,填寫基類虛表地址:
(3)??????填寫派生類虛表地址:
?
(4)??????重新填寫基類虛表地址
?
?
(5)??????插入派生類與基類分割0x000000
虛表:
?
此時派生類的大小為24字節:派生類虛表指針+派生類偏移指針+派生類成員大小+基類虛表指針+基類成員大小+分割的 :0x00000000
?
?
總結
以上是生活随笔為你收集整理的C++多态相关关问题及虚表剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 图片无法删除,php如何删除上
- 下一篇: linux如何打开elf文件格式,lin