C++面向?qū)ο罂偨Y(jié):虛指針與虛函數(shù)表,干貨又來了
最近在逛B站的時(shí)候發(fā)現(xiàn)有候捷老師的課程,如獲至寶。因此,跟隨他的講解又復(fù)習(xí)了一遍關(guān)于C++的內(nèi)容,收獲也非常的大,對(duì)于某些模糊的概念及遺忘的內(nèi)容又有了更深的認(rèn)識(shí)。
以下內(nèi)容是關(guān)于虛函數(shù)表、虛函數(shù)指針,而C++中的動(dòng)態(tài)綁定實(shí)現(xiàn)和這兩個(gè)內(nèi)容是分不開的。

一,虛函數(shù)表、虛指針
當(dāng)一個(gè)類在實(shí)現(xiàn)的時(shí)候,如果存在一個(gè)或以上的虛函數(shù)時(shí),那么這個(gè)類便會(huì)包含一張?zhí)摵瘮?shù)表。而當(dāng)一個(gè)子類繼承并重寫了基類的虛函數(shù)時(shí),它也會(huì)有自己的一張?zhí)摵瘮?shù)表。
當(dāng)我們?cè)谠O(shè)計(jì)類的時(shí)候,如果把某個(gè)函數(shù)設(shè)置成虛函數(shù)時(shí),也就表明我們希望子類在繼承的時(shí)候能夠有自己的實(shí)現(xiàn)方式;如果我們明確這個(gè)類不會(huì)被繼承,那么就不應(yīng)該有虛函數(shù)的出現(xiàn)。
下面是某個(gè)基類A的實(shí)現(xiàn):

從下圖中可以看到該類在內(nèi)存中的存放形式,對(duì)于?虛函數(shù)的調(diào)用是通過查虛函數(shù)表來進(jìn)行的?,每個(gè)虛函數(shù)在虛函數(shù)表中都存放著自己的一個(gè)地址,而如何在?虛函數(shù)表中進(jìn)行查找,則是通過虛指針來調(diào)用?,在內(nèi)存結(jié)構(gòu)中它一般都會(huì)放在類最開始的地方,而對(duì)于普通函數(shù)則不需要通過查表操作。這張?虛函數(shù)表是?什么時(shí)候被創(chuàng)建的呢?它是?在編譯的時(shí)候產(chǎn)生?,否則這個(gè)類的結(jié)構(gòu)信息中也不會(huì)插入虛指針的地址信息。

以下例子包含了繼承關(guān)系:

以上三個(gè)類在內(nèi)存中的排布關(guān)系如下圖所示:

對(duì)于非虛函數(shù),三個(gè)類中雖然都有一個(gè)叫?func2?的函數(shù),但他們彼此互不關(guān)聯(lián),因此都是各自獨(dú)立的,不存在重載一說,在調(diào)用的時(shí)候也不需要進(jìn)行查表的操作,直接調(diào)用即可。
由于子類B和子類C都是繼承于基類A,因此他們都會(huì)存在一個(gè)虛指針用于指向虛函數(shù)表。注意,假如子類B和子類C中不存在虛函數(shù),那么這時(shí)他們將共用基類A的一張?zhí)摵瘮?shù)表,在B和C中用虛指針指向該虛函數(shù)表即可。但是,上面的代碼設(shè)計(jì)時(shí)子類B和子類C中都有一個(gè)虛函數(shù)?vfunc1?,因此他們就需要各自產(chǎn)生一張?zhí)摵瘮?shù)表,并用各自的虛指針指向該表。由于子類B和子類C都對(duì)?vfunc1?作了重載,因此他們有三種不同的實(shí)現(xiàn)方式,函數(shù)地址也不盡相同,在使用的時(shí)候需要從各自類的虛函數(shù)表中去查找對(duì)應(yīng)的?vfunc1?地址。
對(duì)于虛函數(shù)?vfunc2?,兩個(gè)子類都沒有進(jìn)行重載操作,所以基類A、子類B和子類C將共用一個(gè)?vfunc2?,該虛函數(shù)的地址會(huì)分別保存在三個(gè)類的虛函數(shù)表中,但他們的地址是相同的。
從上圖可以發(fā)現(xiàn),在類對(duì)象的頭部存放著一個(gè)虛指針,該虛指針指向了各自類所維護(hù)的虛函數(shù)表,再通過查找虛函數(shù)表中的地址來找到對(duì)應(yīng)的虛函數(shù)。
對(duì)于類中的數(shù)據(jù)而言,子類中都會(huì)包含父類的信息。如上例中的子類C,它自己擁有一個(gè)變量?m_data1?,似乎是和基類中的?m_data1?重名了,但其實(shí)他們并不存在聯(lián)系,從存放的位置便可知曉。
?二,關(guān)于動(dòng)態(tài)綁定
首先來說一說靜態(tài)綁定:?靜態(tài)綁定是指在?程序編譯?過程中,把函數(shù)(方法或者過程)調(diào)用與響應(yīng)調(diào)用所需的代碼結(jié)合的過程(如何理解呢?)
來看一段代碼:


可以看到調(diào)用的卻是派生類的函數(shù)。
在沒有加?virtual?關(guān)鍵字的時(shí)候,通過基類指針指向派生類對(duì)象時(shí),?基類指針只能訪問派生類的成員變量,但是不能訪問派生類的成員函數(shù)。?這是因此在系統(tǒng)編譯過程中,已經(jīng)將area()函數(shù)和shape類綁定在一起了。
而動(dòng)態(tài)綁定是在加了?virtual?關(guān)鍵字以后,派生類中的成員函數(shù)在重寫的時(shí)候會(huì)自動(dòng)生成自己的虛函數(shù)表(單獨(dú)的一個(gè)地址),并通過虛指針指向該地址。
即:shape指針->vptr->Rectangle::area()
通過以上內(nèi)容,我們可以知道在使用基類指針調(diào)用虛函數(shù)的時(shí)候,它能夠根據(jù)所指的類對(duì)象的不同來正確調(diào)用虛函數(shù)。而這些能夠正常工作,得益于虛指針和虛函數(shù)表的引入,使得在程序運(yùn)行期間能夠動(dòng)態(tài)調(diào)用函數(shù)。
動(dòng)態(tài)綁定有以下三項(xiàng)條件要符合:
使用指針進(jìn)行調(diào)用
指針屬于up-cast后的
調(diào)用的是虛函數(shù)
靜態(tài)綁定,他們是類對(duì)象直接可調(diào)用的,而不需要任何查表操作,因此調(diào)用的速度也快于虛函數(shù)。
寫在最后:其實(shí)每個(gè)人都有自己的選擇,學(xué)編程,每一種編程語言的存在都有其應(yīng)用的方向,選擇你想從事的方向,去進(jìn)行合適的選擇就對(duì)了!對(duì)于準(zhǔn)備學(xué)習(xí)編程的小伙伴,如果你想更好的提升你的編程核心能力(內(nèi)功)不妨從現(xiàn)在開始!
微信公眾號(hào):C語言編程學(xué)習(xí)基地
整理分享(多年學(xué)習(xí)的源碼、項(xiàng)目實(shí)戰(zhàn)視頻、項(xiàng)目筆記,基礎(chǔ)入門教程)
歡迎轉(zhuǎn)行和學(xué)習(xí)編程的伙伴,利用更多的資料學(xué)習(xí)成長(zhǎng)比自己琢磨更快哦!
