(給CPP開(kāi)發(fā)者加星標(biāo),提升C/C++技能)
C++ 中的虛函數(shù)的作用主要是實(shí)現(xiàn)了多態(tài)的機(jī)制。關(guān)于多態(tài),簡(jiǎn)而言之就是用父類(lèi)型別的指針指向其子類(lèi)的實(shí)例,然后通過(guò)父類(lèi)的指針調(diào)用實(shí)際子類(lèi)的成員函數(shù)。這種技術(shù)可以讓父類(lèi)的指針有“多種形態(tài)”,這是一種泛型技術(shù)。 虛函數(shù)表每個(gè)含有虛函數(shù)的類(lèi)都有一個(gè)虛函數(shù)表(Virtual Table)來(lái)實(shí)現(xiàn)的。簡(jiǎn)稱(chēng)為V-Table。C++的編譯器應(yīng)該是保證虛函數(shù)表的指針存在于對(duì)象實(shí)例中最前面的位置(這是為了保證取到虛函數(shù)表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。這意味著我們通過(guò)對(duì)象實(shí)例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。 1、 每一個(gè)類(lèi)都有虛函數(shù)列表。 2、 虛表可以繼承,如果子類(lèi)沒(méi)有重寫(xiě)虛函數(shù),那么子類(lèi)虛表中仍然會(huì)有該函數(shù)的地址,只不過(guò)這個(gè)地址指向的是基類(lèi)的虛函數(shù)實(shí)現(xiàn)。如果基類(lèi)3個(gè)虛函數(shù),那么基類(lèi)的虛表中就有三項(xiàng)(虛函數(shù)地址),派生類(lèi)也會(huì)有虛表,至少有三項(xiàng),如果重寫(xiě)了相應(yīng)的虛函數(shù),那么虛表中的地址就會(huì)改變,指向自身的虛函數(shù)實(shí)現(xiàn)。如果派生類(lèi)有自己的虛函數(shù),那么虛表中就會(huì)添加該項(xiàng)。 3、 派生類(lèi)的虛表中虛函數(shù)地址的排列順序和基類(lèi)的虛表中虛函數(shù)地址排列順序相同,子類(lèi)獨(dú)有的虛函數(shù)放在后面。 當(dāng)定義一個(gè)有虛函數(shù)類(lèi)的對(duì)象時(shí),對(duì)象的第一塊的內(nèi)存空間就是一個(gè)指向虛函數(shù)列表的指針。 在這舉個(gè)例子 假設(shè)我們有這樣的一個(gè)類(lèi): 由于例程的操作環(huán)境是64位系統(tǒng),所以用long*強(qiáng)轉(zhuǎn)。其中(long*)(&b)就是虛函數(shù)表地址,(long*)*(long*)(&b)就是第一個(gè)函數(shù)地址,代碼運(yùn)行結(jié)果如下: 在程序中取出對(duì)象b的地址,根據(jù)對(duì)象的布局可以得出就是虛表的地址,根據(jù)這個(gè)地址可以把虛表的第一個(gè)內(nèi)存單元的內(nèi)容取出,然后強(qiáng)制轉(zhuǎn)換成一個(gè)函數(shù)指針,利用這個(gè)函數(shù)指針來(lái)訪問(wèn)虛函數(shù)。又因?yàn)樘摫硎沁B續(xù)的,利用每次+1可以來(lái)訪問(wèn)下一個(gè)內(nèi)存單元。 如果對(duì)代碼不理解的話,可以看這幅圖就會(huì)懂了 注意:虛函數(shù)表在最后會(huì)有一個(gè)結(jié)束標(biāo)志,為1說(shuō)明還有虛表,為0表示沒(méi)有虛表了 。(編譯器不同,結(jié)束標(biāo)志可能存在差異) 下面,將分別具體說(shuō)明“無(wú)虛函數(shù)覆蓋”和“有虛函數(shù)覆蓋”時(shí)的虛函數(shù)表的情況。 (一)無(wú)虛函數(shù)覆蓋沒(méi)有任何的繼承,虛函數(shù)表如下圖 根據(jù)示意圖,編寫(xiě)的代碼如下圖所示: 和上一個(gè)程序一樣,根據(jù)取出虛表里面的地址強(qiáng)制轉(zhuǎn)換成函數(shù)指針,同樣,利用虛表的連續(xù)性,每次指針+1調(diào)用對(duì)應(yīng)的虛函數(shù)??梢缘贸鎏摵瘮?shù)按照其聲明順序存放于虛函數(shù)表中的,子類(lèi)自己的虛函數(shù)是排在父類(lèi)虛函數(shù)之后的。運(yùn)行結(jié)果如下圖 (二)一般繼承(有虛函數(shù)覆蓋) 如果子類(lèi)中有虛函數(shù)重載了父類(lèi)的虛函數(shù),會(huì)是一個(gè)什么樣子?假設(shè),我們有下面這樣的一個(gè)繼承關(guān)系。如圖所示: 在這個(gè)類(lèi)的設(shè)計(jì)中,只覆蓋了父類(lèi)的一個(gè)函數(shù):f()。那么,對(duì)于派生類(lèi)的實(shí)例,其虛函數(shù)表會(huì)是下面的一個(gè)樣子: 從表中可以看到下面幾點(diǎn), 1)覆蓋的f()函數(shù)被放到了虛表中原來(lái)父類(lèi)虛函數(shù)的位置。 2)沒(méi)有被覆蓋的函數(shù)依舊。 這樣就會(huì)出現(xiàn)虛調(diào)用 Base *b = new Derive(); b->f(); 由b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代,于是在實(shí)際調(diào)用發(fā)生時(shí),是Derive::f()被調(diào)用了。這就實(shí)現(xiàn)了多態(tài)。下面我們用一個(gè)示例代碼來(lái)看一下 運(yùn)行結(jié)果如下,確實(shí)如我們以上分析的那樣,由b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代: (三)多重繼承(無(wú)虛函數(shù)覆蓋)下面我們?cè)倏纯炊嘀乩^承的情況 對(duì)于子類(lèi)實(shí)例中的虛函數(shù)表,是下面這個(gè)樣子: 從圖上我們可以看到 1)每個(gè)父類(lèi)都有自己的虛表。 2) 子類(lèi)的成員函數(shù)被放到了第一個(gè)父類(lèi)的表中。(所謂的第一個(gè)父類(lèi)是按照聲明順序來(lái)判斷的) 這樣做就是為了解決不同的父類(lèi)類(lèi)型的指針指向同一個(gè)子類(lèi)實(shí)例,而能夠調(diào)用到實(shí)際的函數(shù)。 下面我們根據(jù)上圖來(lái)實(shí)現(xiàn)一下 運(yùn)行結(jié)果如下: 在這個(gè)程序中,子類(lèi)有多個(gè)父類(lèi),因此從每個(gè)父類(lèi)都繼承了一個(gè)虛表,因此會(huì)有3個(gè)虛表,根據(jù)代碼和運(yùn)行結(jié)果會(huì)發(fā)現(xiàn),排列的順序和繼承的順序一樣,子類(lèi)自己的虛函數(shù)排在第一個(gè)虛表的后面。程序中沒(méi)有改寫(xiě)虛函數(shù) ,因此沒(méi)有覆蓋。同時(shí)主函數(shù)中應(yīng)用的是一個(gè)二重指針,利用二維數(shù)組取每個(gè)虛函數(shù)地址。 (四)多重繼承(有虛函數(shù)覆蓋)下面我們?cè)賮?lái)看看,如果發(fā)生虛函數(shù)覆蓋的情況。 下圖中,我們?cè)谧宇?lèi)中覆蓋了父類(lèi)的f()函數(shù)。 子類(lèi)虛函數(shù)列表如圖所示 三個(gè)父類(lèi)虛函數(shù)表中的f()的位置被替換成了子類(lèi)的函數(shù)指針。這樣,我們就可以任一靜態(tài)類(lèi)型的父類(lèi)來(lái)指向子類(lèi),并調(diào)用子類(lèi)的f()了。 子類(lèi)虛函數(shù)列表訪問(wèn)代碼如下: 程序運(yùn)行結(jié)果如下所示: 本程序中,子類(lèi)重寫(xiě)了f函數(shù),把所有父類(lèi)里面的f函數(shù)都屏蔽了。在虛表中父類(lèi)f函數(shù)的位置全部換成了子類(lèi)f函數(shù)的地址。因此在輸出時(shí)父類(lèi)的f函數(shù)沒(méi)有了,全部是子類(lèi)f函數(shù)的輸出。 - EOF - |
|
來(lái)自: zeroxer2008 > 《待分類(lèi)》
联系客服
微信扫码,添加客服企业微信
客服QQ:
1732698931联系电话:4000-999-276
客服工作时间9:00-18:00,晚上非工作时间,请在微信或QQ留言,第二天客服上班后会立即联系您。