【 聲明:版權所有,歡迎轉載,請勿用於商業用途。 聯系信箱:feixiaoxing @163.com】
虛函數是面向對象設計中的一個重要內容。它的出現使得我們只需要相同的接口函數,並可以得到不同的生成結果。但是有些朋友卻知其然,不知其所以然,為什麼會出現這樣的結果,我們可以用一段代碼說明問題。首先,我們先定義兩個基本類型,一個是employee,一個是manager,看過前面一片博客的朋友應該都有點印象:
class employee
{
public:
employee() { }
~employee() {}
virtual void print() const { printf("employee!\n");}
};
class manager : public employee
{
public:
manager() {}
~manager() {}
void print() const {printf("manager!\n");}
};
class employee
{
public:
employee() { }
~employee() {}
virtual void print() const { printf("employee!\n");}
};
class manager : public employee
{
public:
manager() {}
~manager() {}
void print() const {printf("manager!\n");}
};
我們看到,和前面出現的成員函數稍微有一些不同,這裡的print函數之前出現了virtual。然而正是這個virtual發揮了巨大的作用。可以毫不誇張地說,沒有虛函數,基本上就沒有設計模式,也就無法體現C++語言在面向對象設計中的巨大優越性。下面我們看看這個virtual是怎樣發揮作用的?
76: employee p;
0040128D lea ecx,[ebp-10h]
00401290 call @ILT+45(employee::employee) (00401032)
00401295 mov dword ptr [ebp-4],0
77: manager m;
0040129C lea ecx,[ebp-14h]
0040129F call @ILT+65(manager::manager) (00401046)
004012A4 mov byte ptr [ebp-4],1
78: employee* e = &p;
004012A8 lea eax,[ebp-10h]
004012AB mov dword ptr [ebp-18h],eax
79: e->print();
004012AE mov ecx,dword ptr [ebp-18h]
004012B1 mov edx,dword ptr [ecx]
004012B3 mov esi,esp
004012B5 mov ecx,dword ptr [ebp-18h]
004012B8 call dword ptr [edx]
004012BA cmp esi,esp
004012BC call __chkesp (00408870)
80: e = &m;
004012C1 lea eax,[ebp-14h]
004012C4 mov dword ptr [ebp-18h],eax
81: e->print();
004012C7 mov ecx,dword ptr [ebp-18h]
004012CA mov edx,dword ptr [ecx]
004012CC mov esi,esp
004012CE mov ecx,dword ptr [ebp-18h]
004012D1 call dword ptr [edx]
004012D3 cmp esi,esp
004012D5 call __chkesp (00408870)
82: }
76: employee p;
0040128D lea ecx,[ebp-10h]
00401290 call @ILT+45(employee::employee) (00401032)
00401295 mov dword ptr [ebp-4],0
77: manager m;
0040129C lea ecx,[ebp-14h]
0040129F call @ILT+65(manager::manager) (00401046)
004012A4 mov byte ptr [ebp-4],1
78: employee* e = &p;
004012A8 lea eax,[ebp-10h]
004012AB mov dword ptr [ebp-18h],eax
79: e->print();
004012AE mov ecx,dword ptr [ebp-18h]
004012B1 mov edx,dword ptr [ecx]
004012B3 mov esi,esp
004012B5 mov ecx,dword ptr [ebp-18h]
004012B8 call dword ptr [edx]
004012BA cmp esi,esp
004012BC call __chkesp (00408870)
80: e = &m;
004012C1 lea eax,[ebp-14h]
004012C4 mov dword ptr [ebp-18h],eax
81: e->print();
004012C7 mov ecx,dword ptr [ebp-18h]
004012CA mov edx,dword ptr [ecx]
004012CC mov esi,esp
004012CE mov ecx,dword ptr [ebp-18h]
004012D1 call dword ptr [edx]
004012D3 cmp esi,esp
004012D5 call __chkesp (00408870)
82: }
上面是一段函數調用的代碼,代碼可以稍微有點長。不過沒有關系,我們可以按照代碼的行數一行一行地去進行說明和理解。
76行: 我們創建了employee類型的一個變量p,這個可以從後面的employee的構造函數可以看出來
77行: 我們創建了manager類型的一個變量,這個也可以從後面的manager的構造函數看出
78行: 我們創建一個指針臨時變量e,它保存了變量p的地址,這一句也比較簡單
79行: 我們發現79句下面共有7句匯編,其中第三句、第六句、第七句是平衡堆棧的時候用的,和我們的調用沒有關系。那麼call的edx是什麼東西呢?原來函數調用的順序是這樣的:edx -> [ecx] ->[ebp-0x18],不知道大家看明白了沒有。在內存的第一個字節記錄一個指向print函數指針的指針,也就是edx。通過這個edx,我們就可以查找到位於edx地址的內容是什麼。後來我們提取出來後發現[edx]的內容正是我們要查找的print函數地址。這裡相當於一個二次尋址的過程。
80行: 我們重新對臨時變量e進行了賦值,此時e保存的是變量m的地址
81行: 我們發現此時的尋找過程和79行驚奇地一致,原因就在於edx的內容不同罷了。也就是指向函數指針的指針發生了變化而已。
試想一下,如果沒有這個virtual函數,以上這段代碼會發生什麼差別呢?
76: employee p;
0040127D lea ecx,[ebp-10h]
00401280 call @ILT+45(employee::employee) (00401032)
00401285 mov dword ptr [ebp-4],0
77: manager m;
0040128C lea ecx,[ebp-14h]
0040128F call @ILT+65(manager::manager) (00401046)
00401294 mov byte ptr [ebp-4],1
78: employee* e = &p;
00401298 lea eax,[ebp-10h]
0040129B mov dword ptr [ebp-18h],eax
79: e->print();
0040129E mov ecx,dword ptr [ebp-18h]
004012A1 call @ILT+5(employee::print) (0040100a)
80: e = &m;
004012A6 lea ecx,[ebp-14h]
004012A9 mov dword ptr [ebp-18h],ecx
81: e->print();
004012AC mov ecx,dword ptr [ebp-18h]
004012AF call @ILT+5(employee::print) (0040100a)
82: }
76: employee p;
0040127D lea ecx,[ebp-10h]
00401280 call @ILT+45(employee::employee) (00401032)
00401285 mov dword ptr [ebp-4],0
77: manager m;
0040128C lea ecx,[ebp-14h]
0040128F call @ILT+65(manager::manager) (00401046)
00401294 mov byte ptr [ebp-4],1
78: employee* e = &p;
00401298 lea eax,[ebp-10h]
0040129B mov dword ptr [ebp-18h],eax
79: e->print();
0040129E mov ecx,dword ptr [ebp-18h]
004012A1 call @ILT+5(employee::print) (0040100a)
80: e = &m;
004012A6 lea ecx,[ebp-14h]
004012A9 mov dword ptr [ebp-18h],ecx
81: e->print();
004012AC mov ecx,dword ptr [ebp-18h]
004012AF call @ILT+5(employee::print) (0040100a)
82: }
很遺憾,這裡就沒有了動態查找的過程,所有的打印函數最終都指向了函數employee::print,此時多態性也不復存在了。