c++中為什么可以通過指針或引用實(shí)現(xiàn)多態(tài)詳解
引言:
在c++中司空見慣的事情就是:可以通過指針和引用可以實(shí)現(xiàn)多態(tài),而對(duì)象不可以。 那為什么?讓我們來解開這神秘的暗紗!
1、 類對(duì)象的存儲(chǔ)方式:
在一個(gè)類的實(shí)例中,只會(huì)存放非靜態(tài)的成員變量。 如果該類中存在虛函數(shù)的話,再多加一個(gè)指向虛函數(shù)列表指針—vptr。
例如聲明如下兩個(gè)類,并分別實(shí)例化兩個(gè)對(duì)象,它們的內(nèi)存分配大致如下:(vptr具體在什么位置,與編譯器有關(guān),大多數(shù)都在開始處)
class base { public: virtual ~base() {}; virtual string GetName() { return "base"; } GetA(); int a; }; class derived : public base { public: virtual ~derived() {}; virtual string GetName() { return "derived";} GetB(); int b; };base B1, B2;derived D1, D2;
內(nèi)存分布大致如下:
1. 類對(duì)象中,只有成員變量與vptr.
2. 普通成員函數(shù)在內(nèi)存的某一位置放著。它們與c語(yǔ)言中定義的普通函數(shù)沒有區(qū)別。 當(dāng)我們通過對(duì)象或?qū)ο笾羔樥{(diào)用普通成員函數(shù)時(shí), 編譯器會(huì)拿到它。怎么拿到呢?當(dāng)然是通過名字了,編譯器都會(huì)對(duì)我們寫的函數(shù)的名字進(jìn)行修飾映射,讓它們變成內(nèi)存中唯一的函數(shù)名。
3. 無論基類還是子類,每一種類類型的虛函數(shù)表只有一份,它里面存放了基類的類型信息和指向基類中的虛函數(shù)的指針。 某一類類型的所有對(duì)象都指向了相同的虛函數(shù)表。
2. 無論通過對(duì)象還是指針,能使用的方法只與它們靜態(tài)類型有關(guān)。
例如:下面的 base類型的對(duì)象B1或指針pB1,只能使用GetName() 和GetA()方法。 無論它們是如何來的?。。。?!
// 直接構(gòu)造得到 base B1; base* pB1 = new base(); // 即使從子類轉(zhuǎn)換而來, 通過B1或pB1也永遠(yuǎn)訪問不到GetB()方法。 derived d1; B1 = d1; pB1 = new derived();
3. 不同類型的指針有什么區(qū)別?
本質(zhì)上它們沒有任何區(qū)別,在32/64位系統(tǒng)中都是4/8字節(jié)的一個(gè)變量。 唯一不同的就是編譯器解釋它們的方式,即通過指針來尋址出來的對(duì)象類型不同,大小不同 ,指針類型來告訴編譯器如何解釋該指針。
4. 指針與引用來實(shí)現(xiàn)多態(tài)
有代碼如下 :
derived* _pD = new derived(); base* _pB = _pD; _pB.GetName(); // 返回 derived.
想要知道如何通過指針來實(shí)現(xiàn)的多態(tài),就要看看對(duì)基類指針賦值是發(fā)生了什么! 具體來說 如下圖所示:
我們會(huì)發(fā)現(xiàn),對(duì)指針的賦值,僅僅是讓基類指針_pB指向的子類對(duì)象的地地址。 當(dāng)我們使用基類指針調(diào)用GetName()函數(shù)(該函數(shù)是虛函數(shù),它的地址在函數(shù)表中)時(shí), 會(huì)由_pB指向的地址找到子類的虛函數(shù)表指針vptr_上海,再由vptr_上海在虛函數(shù)表中找到子類的GetName(),從而調(diào)用它。就這樣實(shí)現(xiàn)了多態(tài)。
5. 對(duì)象不能實(shí)現(xiàn)多態(tài)
有代碼如下:
base B1; derived D1; B1 = D1; B1.GetName(); // 返回 base base B2 = D1 B2.GetName(); // 返回 base
上面代碼中無論賦值操作還是賦值構(gòu)造時(shí), 只會(huì)處理成員變量,一個(gè)類對(duì)象里面的vptr永遠(yuǎn)不會(huì)變,永遠(yuǎn)都會(huì)指向所屬類型的虛函數(shù)表,操作如下圖所示:
因此,通過對(duì)象調(diào)用虛函數(shù)時(shí),就沒有必要進(jìn)行動(dòng)態(tài)解析了,白白增加了間接性,浪費(fèi)性能。編譯器直接在編譯時(shí)就可以確認(rèn)具體調(diào)用哪一個(gè)函數(shù)了,因此沒有所謂的多態(tài)。
補(bǔ)充說明:
1. 引用本質(zhì)上也是通過指針的解引用(即*_point)來實(shí)現(xiàn)的,可以<<參考std源碼剖析》一本書,所以引用也可以實(shí)現(xiàn)多態(tài)。
2. 即使通過 基類的指針調(diào)用基類的虛函數(shù) 或 通過子類的指針調(diào)用子類的虛函數(shù) 以及通過子類指針調(diào)用基類的虛函數(shù), 也是通過多態(tài)機(jī)制來完成的(即一步步的間接性來完成)。
3. 一個(gè)空的class的對(duì)象的大小為1個(gè)字節(jié), 編譯器之所以要這么做,是為了區(qū)別同一個(gè)類類型的不同對(duì)象!
總結(jié)
到此這篇關(guān)于c++中為什么可以通過指針或引用實(shí)現(xiàn)多態(tài)的文章就介紹到這了,更多相關(guān)c++指針或引用實(shí)現(xiàn)多態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)LeetCode(557.翻轉(zhuǎn)字符串中的單詞之三)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(557.翻轉(zhuǎn)字符串中的單詞之三),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08配置CLion管理Qt項(xiàng)目國(guó)際化支持的方法
這篇文章主要介紹了配置CLion管理Qt項(xiàng)目國(guó)際化支持的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04對(duì)比C語(yǔ)言中g(shù)etc()函數(shù)和ungetc()函數(shù)的使用
這篇文章主要介紹了對(duì)比C語(yǔ)言中g(shù)etc()函數(shù)和ungetc()函數(shù)的使用,是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08c語(yǔ)言:基于函數(shù)指針的兩個(gè)示例分析
本篇文章是對(duì)c語(yǔ)言中函數(shù)指針的兩個(gè)示例做了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C語(yǔ)言詳解如何實(shí)現(xiàn)帶頭雙向循環(huán)鏈表
帶頭雙向循環(huán)鏈表:結(jié)構(gòu)最復(fù)雜,一般用在單獨(dú)存儲(chǔ)數(shù)據(jù)。實(shí)際中使用的鏈表數(shù)據(jù)結(jié)構(gòu),都是帶頭雙向循環(huán)鏈表。另外這個(gè)結(jié)構(gòu)雖然結(jié)構(gòu)復(fù)雜,但是使用代碼實(shí)現(xiàn)以后會(huì)發(fā)現(xiàn)結(jié)構(gòu)會(huì)帶來很多優(yōu)勢(shì),實(shí)現(xiàn)反而簡(jiǎn)單2022-04-04c語(yǔ)言簡(jiǎn)單實(shí)現(xiàn)文件 r/w 操作方法
由于在 C 語(yǔ)言中 '\' 一般是轉(zhuǎn)義字符的起始標(biāo)志,故在路徑中需要用兩個(gè) '\' 表示路徑中目錄層次的間隔,也可以使用 '/' 作為路徑中的分隔符,本文重點(diǎn)給大家介紹用c語(yǔ)言簡(jiǎn)單實(shí)現(xiàn)文件 r/w 操作方法,感興趣的朋友一起學(xué)習(xí)吧2021-05-05