C++多態簡析——靜態多態與動態多態
多態:顧名思義,多態就是多種形態,也就是對不同對象發送同一個消息,不同對象會做出不同的響應。
並且多態分為靜態多態和動態多態。
靜態多態就是在系統編譯期間就可以確定程序執行到這裡將要執行哪個函數,例如:函數的重載,對象名加點操作符執行成員函數等,都是靜態多態,其中,重載是在形成符號表的時候,對函數名做了區分,從而確定了程序執行到這裡將要執行哪個函數,對象名加點操作符執行成員函數是通過this指針來調用的。
函數的重載比較簡單,不再贅述,這裡我們通過一個簡單的例子來看一下對象名加點操作符執行成員函數的靜態多態:
class A
{
public:
void Set(int a)
{
_a = a;
}
public:
int _a;
};
int main()
{
A a1;
a1.Set(15);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
這裡定義了一個A類,有一個成員函數和一個成員,我們將程序的部分彙編代碼截取出來如下圖:
我們可以看到這裡直接是一個lea指令將a1對象的地址放入寄存器eax中,也就是對象的this指針,然後用call指令就可以跳轉到Set函數,也就是說其彙編代碼在此時就知道應該要去到哪個地方之行哪個函數,這就是靜態多態,也叫編譯時多態。
動態多態則是利用虛函數實現了運行時的多態,也就是說在系統編譯的時候並不知道程序將要調用哪一個函數,只有在運行到這裡的時候才能確定接下來會跳轉到哪一個函數的棧幀。
在說動態多態之前我們先來看一下什麼是虛函數,虛函數就是在基類中聲明該函數是虛擬的(在函數之前加virtual關鍵字),然後在子類中正式的定義(子類中的該函數的函數名,返回值,函數參數個數,參數類型,全都與基類的所聲明的虛函數相同,此時才能稱為重寫,才符合虛函數,否則就是函數的重載),再定義一個指向基類對象的指針,然後使該指針指向由該基類派生的子類對象,再然後用這個指針來調用改虛函數,就能實現動態多態。
下面我們通過一個例子來看一下利用虛函數實現的動態多態:
class A
{
public:
A(int a = 10)
:_a(a)
{}
virtual void Get()
{
cout << "A:: _a=" << _a << endl;
}
public:
int _a;
};
class B : public A
{
public:
B(int b = 20)
:_b(b)
{}
void Get()
{
cout << "B:: _b=" << _b << endl;
}
int _b;
};
int main()
{
A a1;
B b1;
A* ptr1 = &a1;
ptr1->Get();
ptr1 = &b1;
ptr1->Get();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
在這裡我們看到,基類A的Get函數聲明為虛函數,在B類中進行了重寫,
然後在main函數中分別用基類的ptr1和指向子類的ptr2進行調用虛函數Get,我們得到了如下圖的輸出:
打開今日頭條,查看更多精彩圖片這說明確實是實現了不同調用,而且是在運行時,那麼虛函數的底層到底是怎麼實現的呢,我們來看一下彙編代碼及其對象模型:
通過上圖的彙編代碼,我們看到這裡做了一系列的指針解引用處理,最後確定了eax中應該存放的this指針的值,要搞清楚這個必須要搞清楚子類的對象模型。
用監視窗口查看b1可以看到如上圖所示,這裡的_vfptr是一個虛表指針,它指向一個存放該類對象的所有虛函數的地址的表,我們可以將該表理解為一個函數指針數組,在該數組的最後一個元素,編譯系統會將其置為0,。
對象模型如下圖示:
其中紅色為A類的成員,黑色為B類對象b1的成員,紫色就是一個虛函數表,存放著存放該類對象的所有虛函數的地址,彙編代碼做了一系列的指針解引用處理就是為了從虛函數表中找到相應的虛函數進行調用,從而實現了動態多態。
※用順序表求集合的交集、並集和差集
※SSM框架整合裡面的一些配置文件
TAG:程序員小新人學習 |