在我們討論多態的時候,先看看什麼是寫入程式碼和軟編碼:
寫入程式碼就是把代碼寫死了,導致彈性不足,降低了可擴充性,例如在代碼裡的
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++的多態性有一定的協助!謝謝!