深入解析C++中的虛函數(shù)與多態(tài)
1.C++中的虛函數(shù)
C++中的虛函數(shù)的作用主要是實(shí)現(xiàn)了多態(tài)的機(jī)制。關(guān)于多態(tài),簡(jiǎn)而言之就是用父類型別的指針指向其子類的實(shí)例,然后通過(guò)父類的指針調(diào)用實(shí)際子類的成員函數(shù)。這種技術(shù)可以讓父類的指針有“多種形態(tài)”,這是一種泛型技術(shù)。所謂泛型技術(shù),說(shuō)白了就是試圖使用不變的代碼來(lái)實(shí)現(xiàn)可變的算法。比如:模板技術(shù),RTTI技術(shù),虛函數(shù)技術(shù),要么是試圖做到在編譯時(shí)決議,要么試圖做到運(yùn)行時(shí)決議。
對(duì)C++ 了解的人都應(yīng)該知道虛函數(shù)(Virtual Function)是通過(guò)一張?zhí)摵瘮?shù)表(Virtual Table)和一個(gè)指向虛函數(shù)表的指針(vptr)來(lái)實(shí)現(xiàn)的。虛函數(shù)表,簡(jiǎn)稱為vtbl,虛函數(shù)表表對(duì)實(shí)現(xiàn)多態(tài)起著至關(guān)重要的作用。在這個(gè)表中,主要保存了一個(gè)類中的虛函數(shù)的地址,這張表解決了繼承、覆蓋的問(wèn)題,保證其內(nèi)容能真實(shí)反應(yīng)實(shí)際的函數(shù)。每一個(gè)包含虛函數(shù)的類的實(shí)例都包含一個(gè)cptr指針,指向虛函數(shù)表的首地址。我們可以通過(guò)這個(gè)指針找到要訪問(wèn)的虛函數(shù)的,完成虛函數(shù)的調(diào)用主要包括:找到虛函數(shù)表的首地址(vptr),通過(guò)cptr找到要使用虛函數(shù)地址,調(diào)用虛函數(shù)。那么使用虛函數(shù)大家總要考慮效率的問(wèn)題,實(shí)際上為了提高效率,C++的編譯器是保證虛函數(shù)表的指針存在于對(duì)象實(shí)例中最前面的位置,這是為了保證取到虛函數(shù)表的有最高的性能,這意味著我們通過(guò)對(duì)象實(shí)例的地址得到這張?zhí)摵瘮?shù)表,然后通過(guò)遍歷表就可以找到其中的虛函數(shù)的地址,然后調(diào)用相應(yīng)的函數(shù)。不妨看看下面的代碼:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
typedef void(*Fun)(void);
int main()
{
Base b;
Fun pFun = NULL;
cout << "虛函數(shù)表地址:" << (int*)(&b) << endl;
cout << "虛函數(shù)表 — 第一個(gè)函數(shù)地址:" << (int*)*(int*)(&b) << endl;
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
return 0;
}
通過(guò)這個(gè)示例,可以看到,通過(guò)強(qiáng)行把&b轉(zhuǎn)成int *,取得虛函數(shù)表的地址(vptr),然后,再次取址就可以得到第一個(gè)虛函數(shù)的地址了,也就是Base::f(),這在上面的程序中得到了驗(yàn)證(把int* 強(qiáng)制轉(zhuǎn)成了函數(shù)指針)。通過(guò)這個(gè)示例,我們就可以知道如果要調(diào)用Base::g()和Base::h(),其代碼如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
可以看看虛函數(shù)表的圖是怎么畫(huà)的:
大家都知道,多態(tài)是通過(guò)繼承實(shí)現(xiàn)的,那么我們要說(shuō)說(shuō)虛函數(shù)繼承的問(wèn)題。繼承就涉及到了虛函數(shù)的覆蓋了,實(shí)際上不被覆蓋的虛函數(shù)和多態(tài)又有什么聯(lián)系呢?這里我們討論有覆蓋的虛函數(shù)表是什么樣的,假設(shè)存在下面的繼承關(guān)系:
看看虛函數(shù)表示什么樣的:
可以發(fā)現(xiàn),Base::f()被覆蓋了,這樣若把Derive的實(shí)例賦值給一個(gè)基類Base指針pBase,通過(guò)pBase->f();則訪問(wèn)的是子類中的f()也就是完成了多態(tài)。那么虛函數(shù)表中的內(nèi)容到底是怎么樣的呢?可以看看下面的四句話就會(huì)明白!
1.虛函數(shù)按照其聲明順序放于表中。
2.父類的虛函數(shù)在子類的虛函數(shù)前面。
3.覆蓋的f()函數(shù)被放到了虛表中原來(lái)父類虛函數(shù)的位置。
4.沒(méi)有被覆蓋的函數(shù)依舊。
2.用虛函數(shù)實(shí)現(xiàn)多態(tài)
看看下面的多態(tài)的代碼:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void Print()
{
cout<<"Base::Print()"<<endl;
}
};
class Derive : public Base
{
public:
virtual void Print()
{
cout<<"Derive::Print()"<<endl;
}
};
int main()
{
Derive derive;
Base *pBase = &derive;
pBase->Print();
return 0;
}
//多態(tài)代碼
實(shí)現(xiàn)虛函數(shù)的代碼,一定要切記:一定是基類的指針指向子類的對(duì)象的地址。首先試著理解一下用虛函數(shù)實(shí)現(xiàn)多態(tài)的原理,如果實(shí)在沒(méi)理解為什么虛函數(shù)能實(shí)現(xiàn)多態(tài),又為什么這樣實(shí)現(xiàn)多態(tài),上網(wǎng)再搜一搜相關(guān)的資料?。?!
相關(guān)文章
vscode使用官方C/C++插件無(wú)法進(jìn)行代碼格式化問(wèn)題
這篇文章主要介紹了vscode使用官方C/C++插件無(wú)法進(jìn)行代碼格式化問(wèn)題,本文通過(guò)截圖實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04基于MFC和OpenCV實(shí)現(xiàn)角點(diǎn)檢測(cè)
這篇文章主要為大家詳細(xì)介紹了基于MFC和OpenCV實(shí)現(xiàn)角點(diǎn)檢測(cè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03C++學(xué)習(xí)之Lambda表達(dá)式的用法詳解
Lambda?表達(dá)式(lambda?expression)是一個(gè)匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名。本文就來(lái)為大家詳細(xì)講講C++中Lambda表達(dá)式的使用,需要的可以參考一下2022-07-07C語(yǔ)言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法,涉及系統(tǒng)函數(shù)atoi()函數(shù)的使用技巧,需要的朋友可以參考下2014-12-12c語(yǔ)言結(jié)構(gòu)體字節(jié)對(duì)齊的實(shí)現(xiàn)方法
在c語(yǔ)言的結(jié)構(gòu)體里面一般會(huì)按照某種規(guī)則去進(jìn)行字節(jié)對(duì)齊。本文就來(lái)介紹一下如何實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解下2021-07-07基于matlab對(duì)比度和結(jié)構(gòu)提取的多模態(tài)解剖圖像融合實(shí)現(xiàn)
這篇文章主要介紹了多模態(tài)醫(yī)學(xué)圖像配準(zhǔn)與融合的概念、方法及意義,最后簡(jiǎn)單介紹了小波變換分析方法。感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2021-11-11