在我們討論多態的時候,先看看什麼是硬編碼和軟編碼:硬編碼就是把代碼寫死了,導致彈性不足,降低了可擴展性,例如在代碼裡的if……else……;switch……case……
這些代碼通常都屬於硬編碼,項目中的這些代碼多了,就相當於說明這個代碼的靈活性、擴展性、彈性等等的少了。
所以,我們要盡量使用軟編碼,通俗點就是“別把話說死了,留點轉彎的余地”。多態性就是這種軟編碼特性的反映,下面我們一起來研究一下多態性。
多態性是一種抽象,把事物的特征抽象出來,然後事物的具體形態我們就不關心了。
例如對工人這種事物來說,他的特征就是工作,至於是什麼工人,他做什麼工作,我們就不用關心了,只要我們以“工人。工作”這種方式去調用。那他就會為我們工作了。
那為什麼我們不抽象出其他的特征,只抽象出工作這個特征呢?因為我們只對這個特征感興趣,他的什麼吃飯、睡覺、如廁等的特性我們都不關心了。有了多態,我們就可以實現軟編碼了!
講解了多態的概念之後,我們來看看多態的實現(C++的實現):
多態的實現是通過虛函數表(VTable),每個類如果有虛函數,那它就有一個虛函數表,所有的對象都共享這一個VTable.這個概念也叫做動態聯編,還有靜態聯編,這些概念都是通過在程序執行的時候表現出來的性質來定的,我們下面會看看它的“動”和“靜”究竟體現在哪裡。
先看一段代碼:
class C0
...{
public:
void Test()
...{
cout << "call C0 Test()。" << endl;
}
};
這個類沒有虛函數,調用的時候就是靜態調用。調用的代碼如下:
// 靜態編譯(早綁定 early binding)
C0 *pO0;
C0 obj0;
pO0 = &obj0;
pO0->Test();
它的反匯編代碼如下:
// 直接調用函數(已經知道地址)
00401432 mov ecx,dWord ptr [ebp-0Ch]
00401435 call @ILT+160(C0::Test) (004010a5)
下面看看帶虛函數的類:
class C1
...{
public:
virtual void Test()
...{
cout << "call C1 Test()" << endl;
}
};
class C11 : public C1
...{
public:
void Test()
...{
cout << "call C11 Test()" << endl;
}
};
它的調用:
C11 obj11;
C1 *pObj1;
pObj1 = &obj11;
// 這裡生成的匯編代碼
// 0040144A lea edx,[ebp-14h] // 尋址找到pObj1
// 0040144D mov dword ptr [ebp-1Ch],edx
pObj1->Test();
// 這裡生成的匯編代碼
// 00401450 mov eax,dword ptr [ebp-1Ch] // 取得虛表地址
// 00401453 mov edx,dword ptr [eax]
// 00401455 mov esi,esp
// 00401457 mov ecx,dword ptr [ebp-1Ch] //
根據虛表的位置來取得Test()函數
// 0040145A call dword ptr [edx] // 調用Test()函數
根據上述的匯編代碼,我們可以知道,在多態調用函數的時候,程序執行以下步驟:
1、尋址找到pObj1
2、由於C11重載了Test虛函數,所以*pObj1指向的就是C11的VTable的地址
3、調用pObj1->Test()時,程序通過Vptr(虛表的指針,對象的首地址),找到VTable,再根據偏移調用Test函數。
由於上述的多態調用過程是一個動態的過程(在運行時去“找”函數來調用),而不是編譯完就直接把函數地址擺在那裡了,所以被稱作“動態聯編”。
上面把多態的“動”和“靜”的特點結合代碼說了一遍,希望能說清楚了。
下面再驗證一個類的虛表的問題,如果你對虛表已經很熟悉了,就不用再往下看了。
在很多書上都已經說明了C++的對象模型,這裡只是做個驗證。看看這段代碼:
class C1
...{
public:
virtual void Test()
...{
cout << "call C1 Test()" << endl;
}
};
class C11 : public C1
...{
public:
void Test()
...{
cout << "call C11 Test()" << endl;
}
};
class C12 : public C1
...{
public:
void Test()
...{
cout << "call C12 Test()" << endl;
}
};
我們可以知道 Test() 是虛函數,從C1派生的類必定有自己的虛表。而且根據別的資料,虛表指針是放在對象的首地址的,我們下面就來驗證一下:
// 驗證首地址
C11 obj110;
C11 obj111;
printf("obj110 的地址:%x ", &obj110);
printf("obj111 的地址:%x ", &obj111);
printf("obj110 虛表的地址:%x ", *(&obj110));
printf("obj111 虛表的地址:%x ", *(&obj111));
結果是:
obj110 的地址:12ff7c
obj111 的地址:12ff78
obj110 虛表的地址:432098
obj111 虛表的地址:432098
由上面的結果我們可以驗證:
1、一個類一個VTABLE,而不是一個對象一個VTABLE。
2、對象的首地址的內容就是VTABLE的地址。
總結一下:
C++的多態性包括其概念和實現,本文從編譯器生成的代碼來討論C++多態特性,特別說明了為什麼多態特性被稱為“動態聯編”,它和“靜態聯編”有什麼不同,它們的“動”與“靜”體現在哪裡。另外還對對象的虛表做了些驗證。好了,希望本文能對你認識C++的多態性有一定的幫助!