測試環境:win10 64位 vs2013
一些概念:
虛函數:類的成員函數前面加上virtual關鍵字,則此成員函數即爲虛函數。
重寫:在子類定義了一個與父類完全相同的虛函數,則稱子類的虛函數重寫了父類的虛函數。
多態:多態就是多種形態,C++的多態分爲靜態多態和動態多態。靜態多態就是重載,因爲在編譯期間決定調用哪個函數,所以稱爲靜態多態;動態多態是通過繼承重寫基類的虛函數實現的多態,因爲是在運行期間決定調用哪個函數,所以稱爲動態多態。
1.單繼承對象模型
#pragma once
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f1()
{
cout << "Base::f1" << endl;
}
virtual void f2()
{
cout << "Base::f2" << endl;
}
private:
int _a;
};
class Derive :public Base
{
public:
virtual void f1()
{
cout << "Derive::f1" << endl;
}
virtual void f3()
{
cout << "Derive::f3" << endl;
}
private:
int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout << "虛表地址:" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d個虛函數地址 :0%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void test1()
{
Base b1;
Derive d1;
int* VTable1 = (int*)(*(int*)&b1);
int* VTable2 = (int*)(*(int*)&d1);
PrintVTable(VTable1);
PrintVTable(VTable2);
}
單繼承原理:
對於b1: 在構造時,爲所有虛函數創建一個虛表,虛表首地址存在對象b1的首地址中。
對於d1: 在構造時,同樣要建立一個虛表,由於繼承了父類;所以首先構造父類內成員,然後再構造d1內其他成員,在構造父類後,由於存在重寫f1。所以將d1中f1覆蓋到父類f1位置,以下爲測試。
內存分佈:
運行代碼發現在監視窗口並沒有顯示d1完整的虛函數地址,打開內存窗口,輸入d1的虛表地址。
上述數據基本符合預期,爲了更直觀,將上述虛函數地址打印,如下:
現在在Base裏添加兩個Display()函數,一個傳參,一個不傳,使他們構成重載。
void test2()
{
Derive d2;
Base& b2 = d2;
b2.f1();
b2.Display();
b2.Display(1);
}
查看反彙編,明顯得出動態多態與靜態在尋址時極爲不同的方式
b2.f1(); ;動態多態,虛表尋找地址
mov eax,dword ptr [b2] ;b2地址給eax
mov edx,dword ptr [eax] ;將eax指向的內容給edx
mov esi,esp
mov ecx,dword ptr [b2] ;b2地址給eax
mov eax,dword ptr [edx] ;將edx內容給eax
call eax
cmp esi,esp
call __RTC_CheckEsp (01B1361h)
b2.Display(); ;靜態多態,編譯時確定地址
mov ecx,dword ptr [b2]
call Base::Display (01B124Eh)
b2.Display(1);
push 1
b2.Display(1);
mov ecx,dword ptr [b2]
call Base::Display (01B10E1h)
2.多重繼承對象模型
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void f1()
{
cout<<"Base1::f1"<<endl;
}
virtual void f2()
{
cout<<"Base1::f2"<<endl;
}
private:
int _b1;
};
class Base2
{
public:
virtual void f1()
{
cout<<"Base2::f1"<<endl;
}
virtual void f2()
{
cout<<"Base2::f2"<<endl;
}
private:
int _b2;
};
class Derive: public Base2,public Base1
{
public:
virtual void f1()
{
cout<<"Derive::f1"<<endl;
}
virtual void f3()
{
cout<<"Derive::f3"<<endl;
}
private:
int _d1;
};
typedef void(*FUNC)();
void PrintVTable(int* VTable)
{
cout << "虛表地址:" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d個虛函數地址 :0x%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void test1()
{
Derive d;
PrintVTable((int*)(*((int*)&d)));
PrintVTable((int*)(*((int*)((char*)&d + sizeof(Base1)))));
}
多重繼承原理:
在重多繼承過程中,子類的成員函數重寫(上例f1),如果兩個父類同時包含重寫的函數,將子類的其他虛函數存在優先繼承的虛函數表中(上例先繼承Base2)。
內存分佈:
打開監視窗口仍然發現不能顯示全部虛函數的地址,如下:
然後我們可以打印出虛函數地址,如下:
於是我們得到多重繼承模型:
3.菱形虛擬繼承
#include <iostream>
using namespace std;
class A
{
public:
virtual void f1()
{}
int _a;
};
class B : virtual public A
{
public:
virtual void f1()
{}
virtual void f2()
{
cout << "D::f2()" << endl;
}
int _b;
};
class C : virtual public A
{
public:
virtual void f1()
{}
virtual void f2()
{
cout << "D::f2()" << endl;
}
int _c;
};
class D : public B, public C
{
public:
virtual void f1()
{
cout << "D::f1()" << endl;
}
virtual void f3()
{
cout << "D::f3()" << endl;
}
int _d;
};
void test1()
{
D d1;
d1._a = 1;
d1._b = 2;
d1._c = 3;
d1._d = 4;
PrintVTable((int*)(*(int*)&d1));
PrintVTable((int*)(*((int*)&d1 + 3))); //+3是因爲用sizeof計算崩潰
PrintVTable((int*)(*((int*)&d1 + 7))); //所以對照內存表人工計算
}
通過查看內存分配和打印每個虛表內函數地址,畫出如下d1模型圖,由於虛繼承將A構造的對象,放置在了最下面,然後有一虛基表指向此處,以後可以訪問。