詳解C++ 多態(tài)的實現(xiàn)及原理
C++的多態(tài)性用一句話概括就是:在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時將會根據(jù)對象的實際類型來調(diào)用相應(yīng)的函數(shù)。如果對象類型是派生類,就調(diào)用派生類的函數(shù);如果對象類型是基類,就調(diào)用基類的函數(shù)
1:用virtual關(guān)鍵字申明的函數(shù)叫做虛函數(shù),虛函數(shù)肯定是類的成員函數(shù)。
2:存在虛函數(shù)的類都有一個一維的虛函數(shù)表叫做虛表,類的對象有一個指向虛表開始的虛指針。虛表是和類對應(yīng)的,虛表指針是和對象對應(yīng)的。
3:多態(tài)性是一個接口多種實現(xiàn),是面向?qū)ο蟮暮诵模譃轭惖亩鄳B(tài)性和函數(shù)的多態(tài)性。
4:多態(tài)用虛函數(shù)來實現(xiàn),結(jié)合動態(tài)綁定.
5:純虛函數(shù)是虛函數(shù)再加上 = 0;
6:抽象類是指包括至少一個純虛函數(shù)的類。
純虛函數(shù):virtual void fun()=0
;即抽象類!必須在子類實現(xiàn)這個函數(shù),即先有名稱,沒有內(nèi)容,在派生類實現(xiàn)內(nèi)容。
下面看下c++語言虛函數(shù)實現(xiàn)多態(tài)的原理
自上一個帖子之間跳過了一篇總結(jié)性的帖子,之后再發(fā),今天主要研究了c++語言當(dāng)中虛函數(shù)對多態(tài)的實現(xiàn),感嘆于c++設(shè)計者的精妙絕倫
c++中虛函數(shù)表的作用主要是實現(xiàn)了多態(tài)的機(jī)制。首先先解釋一下多態(tài)的概念,多態(tài)是c++的特點(diǎn)之一,關(guān)于多態(tài),簡而言之就是 用父類的指針指向其子類的實例,然后通過父類的指針調(diào)用實際子類的成員函數(shù),這種方法呢,可以讓父類的指針具有多種形態(tài),也就是說不需要改動很多的代碼就可以讓父類這一種指針,干一些很多子類指針的事情,這里是從虛函數(shù)的實現(xiàn)機(jī)制層面進(jìn)行研究
在寫這篇帖子之前對于相關(guān)的文章進(jìn)行了查閱,基本上是大段的文字,所以我的這一篇可能會用大量的圖形進(jìn)行贅述(如果理解有誤的地方,煩請大佬能夠指出),接下來就言歸正傳:
首先介紹一下為什么會引進(jìn)多態(tài)呢,基于c++的復(fù)用性和拓展性而言,同類的程序模塊進(jìn)行大量重復(fù),是一件無法容忍的事情,比如我設(shè)置了蘋果,香蕉,西瓜類,現(xiàn)在想把這些東西都裝到碗這個函數(shù)里,那么在主函數(shù)當(dāng)中,聲明對象是必須的,但是每一次裝進(jìn)碗里對于水果來說,都要用自己的指針調(diào)用一次裝的功能,那為什么不把這些類抽象成一個水果類呢,直接定義一個水果類的指針一次性調(diào)用所有水果裝的功能呢,這個就是利用父類指針去調(diào)用子類成員,但是這個思想受到了指針指向類型的限制,也就是說表面指針指向了子類成員,但實際上還是只能調(diào)用子類成員里的父類成員,這樣的思想就變的毫無意義了,如果想要解決這個問題,只要在父類前加上virtual就可以解決了,這里就是利用虛函數(shù)實現(xiàn)多態(tài)的實例。
首先還是作為舉例來兩個類,在之前基礎(chǔ)知識的帖子中提到過,空類的大小是一個字節(jié)(占位符),函數(shù),靜態(tài)變量都在編譯期就形成了,不用類去分配空間,但是做一個小實驗,看一看在定義了虛函數(shù)之后,類的大小是多少呢
#include<iostream> using namespace std; class CFather { public: virtual void AA() //虛函數(shù)標(biāo)識符 { cout << "CFather :: AA()" << endl; } void BB() { cout << "CFather :: BB()" << endl; } }; class CSon : public CFather { public: void AA() { cout << "CSon :: AA()" << endl; } void BB() { cout << "CSon :: BB()" << endl; } }; int main() { cout << sizeof(CFather) << endl; //測試加了虛函數(shù)的類 system("pause"); return 0; }
很明顯類里裝了一個 4個字節(jié)的東西,除了整形int,就是指針了,沒錯這里裝的就是函數(shù)指針
先把這個代碼,給抽象成圖形進(jìn)行理解,在這CFather為A,CSon為B
此時就是一個單純的繼承的情況,不存在虛函數(shù),然后我new一個對象,A *p = new A;那么 p -> AA(),必然是指向A類中的AA()函數(shù),那么函數(shù)的調(diào)用有兩種方式 一種函數(shù)名加()直接調(diào)用,一種是利用函數(shù)指針進(jìn)行調(diào)用,在這里我想要調(diào)用子類的,就可以利用函數(shù)指針進(jìn)行調(diào)用,假設(shè)出來兩個函數(shù)指針,來指向B類中的兩個成員函數(shù),如果我父類想要調(diào)用子類成員,就可以通過 p指針去調(diào)用函數(shù)指針,再通過函數(shù)指針去調(diào)用成員函數(shù)
,
每一個函數(shù)都可以用一個函數(shù)指針去指著,那么每一類中的函數(shù)指針都可以形成自己的一個表,這個就叫做虛函數(shù)表
那么在創(chuàng)建對象后,為什么類中會有四個字節(jié)的內(nèi)存空間呢?
在C++的標(biāo)準(zhǔn)規(guī)格說明書中說到,編譯器必需要保證虛函數(shù)表的指針存在于對象中最前面的位置(這是為了保證正確取到虛函數(shù)的偏移量)。這意味著我們通過對象實例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。也就是說這四個字節(jié)的指針,代替了上圖中(p->*pfn)()的作用,指向了函數(shù)指針,也就是說,在使用了虛函數(shù)的父類成員函數(shù),雖然寫的還是p->AA(),實際上卻是,(p->*(vfptr[0])),而指向哪個虛函數(shù)表就由,創(chuàng)建的對象來決定
至此,就能理解如何用虛函數(shù)這個機(jī)制來實現(xiàn)多態(tài)的了
下面,我將分別說明“無覆蓋”和“有覆蓋”時的虛函數(shù)表的樣子。沒有覆蓋父類的虛函數(shù)是毫無意義的。我之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。在比較之下,我們可以更加清楚地知道其內(nèi)部的具體實現(xiàn)。
無虛數(shù)覆蓋
下面,再讓我們來看看繼承時的虛函數(shù)表是什么樣的。假設(shè)有如下所示的一個繼承關(guān)系:
請注意,在這個繼承關(guān)系中,子類沒有重載任何父類的函數(shù)。那么,在派生類的實例中,Derive d; 的虛函表:
我們可以看到下面幾點(diǎn):
1)虛函數(shù)按照其聲明順序放于表中。
2)父類的虛函數(shù)在子類的虛函數(shù)前面。
有虛數(shù)覆蓋
覆蓋父類的虛函數(shù)是很顯然的事情,不然,虛函數(shù)就變得毫無意義。下面,我們來看一下,如果子類中有虛函數(shù)重載了父類的虛函數(shù),會是一個什么樣子?假設(shè),我們有下面這樣的一個繼承關(guān)系。
為了讓大家看到被繼承過后的效果,在這個類的設(shè)計中,我只覆蓋了父類的一個函數(shù):f()。那么,對于派生類的實例,其虛函數(shù)表會是下面的一個樣子:
我們從表中可以看到下面幾點(diǎn),
1)覆蓋的f()函數(shù)被放到了虛表中原來父類虛函數(shù)的位置。
2)沒有被覆蓋的函數(shù)依舊。
這樣,我們就可以看到對于下面這樣的程序,
Base *b = new Derive();
b->f();
由b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代,于是在實際調(diào)用發(fā)生時,是Derive::f()被調(diào)用了。這就實現(xiàn)了多態(tài)。
總結(jié)
以上所述是小編給大家介紹的c++語言虛函數(shù)實現(xiàn)多態(tài)的原理,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
相關(guān)文章
C語言算法練習(xí)之?dāng)?shù)組求素數(shù)
這篇文章主要為大家介紹了C語言算法練習(xí)中數(shù)組求素數(shù)的實現(xiàn)方法,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C語言有一定幫助,需要的可以參考一下2022-09-09快速了解C語言靜態(tài)關(guān)鍵字static的作用
這篇文章主要介紹了C語言中靜態(tài)關(guān)鍵字static的作用,對大家學(xué)習(xí)C語言非常有幫助,有需求的小伙伴可以參考下2020-05-05C語言數(shù)據(jù)結(jié)構(gòu)時間復(fù)雜度及空間復(fù)雜度簡要分析
我們在進(jìn)行編程時,往往會開發(fā)諸多的算法,那么我們怎么在那么多算法中找到最好的那個呢?本文主要介紹時間和空間復(fù)雜度概念及時間復(fù)雜度的求解,預(yù)祝讀者學(xué)習(xí)愉快2021-10-10淺析c#中如何在form的webbrowser控件中獲得鼠標(biāo)坐標(biāo)
以下是對c#中如何在form的webbrowser控件中獲得鼠標(biāo)坐標(biāo)的實現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下2013-07-07