C++ 基礎(chǔ)教程之虛函數(shù)實(shí)例代碼詳解
虛函數(shù)的定義
虛函數(shù):就是在基類的成員函數(shù)前加關(guān)鍵字virtual(即被virtual關(guān)鍵字修飾的成員函數(shù)),并在一個(gè)或多個(gè)派生類中被重新定義的成員函數(shù);虛函數(shù):就是在編譯的時(shí)候不確定要調(diào)用哪個(gè)函數(shù),而是動(dòng)態(tài)決定將要調(diào)用哪個(gè)函數(shù)。它的作用就是為了能讓這個(gè)函數(shù)在它的子類里面可以被重載,這樣的話,編譯器就可以使用后期綁定來(lái)達(dá)到多態(tài)了,也就是用基類的指針來(lái)調(diào)用子類的這個(gè)函數(shù);虛函數(shù)的作用:在于用專業(yè)術(shù)語(yǔ)來(lái)解釋就是實(shí)現(xiàn)多態(tài)性,多態(tài)性是將接口與實(shí)現(xiàn)進(jìn)行分離,通過(guò)指向派生類的基類指針或引用,訪問(wèn)派生類中同名覆蓋成員函數(shù);用形象的語(yǔ)言來(lái)解釋就是實(shí)現(xiàn)以共同的方法,但因個(gè)體差異,而采用不同的策略;虛函數(shù)用法格式為:virtual 函數(shù)返回類型 函數(shù)名(參數(shù)表) {函數(shù)體};
虛函數(shù)在 c++ 的繼承體系中是一個(gè)非常重要概念,讓我們可以在子類中復(fù)寫父類的方法。學(xué)到這里我還不知道在 c++ 中是否有抽象類的概念,那么學(xué)習(xí)過(guò)虛函數(shù)我們就知道通過(guò)(純)虛函數(shù)可以實(shí)現(xiàn) java 中的抽象類,
要實(shí)現(xiàn)虛函數(shù)需要兩個(gè)步驟進(jìn)行修改
- 在父類中,在函數(shù)GetName()前面加上virtual
- 在子類中,在函數(shù)GetName()后面加上override
再次運(yùn)行編譯運(yùn)行程序就得到我們想要結(jié)果了
Shap rectangle
下面開(kāi)始正文
virtual 函數(shù)
示例代碼如下:
#include <stdio.h> class base { public: virtual void name(){printf("base\n");}; virtual ~base(){}; }; class plus: public base { public: virtual void name(){printf("plus\n");}; }; void fv(base b){ b.name(); } void fp(base &b){ b.name(); } int main(){ base b; plus p; fv(b); fv(p); fp(b); fp(p); return 0; }
程序輸出:
base base base plus
這里涉及到一個(gè)c++知識(shí)點(diǎn)-- 向上強(qiáng)制轉(zhuǎn)換 :將派生類引用或指針轉(zhuǎn)換為基類引用或指針。該規(guī)則使得公有繼承不需要進(jìn)行顯示類型轉(zhuǎn)化,它是is-a 規(guī)則的一部分。
相反的過(guò)程被稱為-- 向下強(qiáng)制轉(zhuǎn)換 ,向下強(qiáng)制類型轉(zhuǎn)換必須是顯示的。因?yàn)榕缮惪赡軐?duì)基類進(jìn)行拓展,新增的成員變量和函數(shù)不能應(yīng)用于基類。
隱式向上強(qiáng)制轉(zhuǎn)換使得基類指針或引用可以指向基類對(duì)象或派生類對(duì)象,因此需要 動(dòng)態(tài)聯(lián)編 。C++ 使用虛成員函數(shù)函數(shù)滿足這種需求。
動(dòng)態(tài)聯(lián)編
編譯器在編譯時(shí)要將調(diào)用的函數(shù)對(duì)應(yīng)相應(yīng)的可執(zhí)行代碼,此過(guò)程為 函數(shù)聯(lián)編(binding) ,在C++因?yàn)楹瘮?shù)重載的原因,需要查看調(diào)用函數(shù)名和傳入?yún)?shù)才能確認(rèn)是哪一個(gè)函數(shù)。在編譯的時(shí)候可以確認(rèn)使用哪一個(gè)函數(shù)的聯(lián)編被稱為 靜態(tài)聯(lián)編 或 早期聯(lián)編 。
同時(shí)因?yàn)関irtual函數(shù)的存在,編譯工作變得更加復(fù)雜,如示例函數(shù)所示,具體使用的哪個(gè)類型對(duì)象不能確認(rèn)。為此編譯器必須生成一些代碼,使得在程序運(yùn)行的時(shí)候選擇正確的虛函數(shù),這被稱為 動(dòng)態(tài)聯(lián)編 ,又被稱為 晚期聯(lián)編 。
為了驗(yàn)證上面所述我們可以做一組對(duì)照,首先我們用 gnu 工具 nm 來(lái)查看 sysbols,可以發(fā)現(xiàn)如下的部分:
$ nm virtual.exe | grep -c -E "plus|base"
然后我們改造一下上面的代碼:
class base { public: void name(){printf("base\n");}; // 修改 virtual ~base(){}; }; class plus: public base { public: void name(){printf("plus\n");}; // 修改 };
編譯后重新執(zhí)行 nm 命令:
nm virtual_.exe | grep -c -E "plus|base" 45
經(jīng)過(guò)比對(duì)后我們會(huì)發(fā)現(xiàn)修改后缺少了以下symbols:
000000000040509c p .pdata$_ZN4plus4nameEv 0000000000402e00 t .text$_ZN4plus4nameEv 00000000004060a0 r .xdata$_ZN4plus4nameEv 0000000000402e00 T _ZN4plus4nameEv
動(dòng)態(tài)聯(lián)編在效率上要低于靜態(tài)聯(lián)編,在C++ 中默認(rèn)使用靜態(tài)聯(lián)編。C++ 之父strousstup 認(rèn)為 C++ 指導(dǎo)原則之一是不要為不使用的特性付出代價(jià)(cpu、memory等)。
所以在派生類不需要去重寫基類函數(shù)時(shí),則不要將其聲明為virtual函數(shù)。
virtual 函數(shù)工作原理
虛函數(shù)表示每一個(gè)使用C++的開(kāi)發(fā)者耳熟能詳?shù)臇|西,有一個(gè)道經(jīng)典的試題如下:
#include <stdio.h> class base { public: base(){}; virtual ~base() { printf("base\n"); }; }; class plus : public base { public: plus(/* args */){}; virtual ~plus() { printf("plus\n"); }; }; class plus2 : public base { public: plus2(/* args */){}; ~plus2() { printf("plus2\n"); }; }; class plus3 : public base { public: virtual void name() { printf("plus3"); }; plus3(/* args */){}; virtual ~plus3() { printf("plus3\n"); }; }; class empty { private: /* data */ public: empty(/* args */){}; ~empty() { printf("empty\n"); }; }; int main() { base b; printf("base: %d\n", sizeof(b)); plus p; printf("plus: %d\n", sizeof(p)); plus2 p2; printf("plus2: %d\n", sizeof(p2)); plus3 p3; printf("plus3: %d\n", sizeof(p3)); empty e; printf("empty: %d\n", sizeof(e)); }
其最終輸出的結(jié)果如下:
base: 8 plus: 8 plus2: 8 plus3: 8 empty: 1 empty plus3 base plus2 base plus base base
ps: 由于操作系統(tǒng)位數(shù)的影響結(jié)果可能有變動(dòng),在x64位系統(tǒng)中指針內(nèi)存分配大小為 8 字節(jié),x86 系統(tǒng)中指針內(nèi)存分配大小為 4。
我們可以清楚的看到,只要存在虛函數(shù)不論是成員函數(shù)異或是析構(gòu)函數(shù),是在類中定義或繼承都會(huì)有包含一個(gè)虛函數(shù)表。而這里的8字節(jié)就是分配給了虛函數(shù)表的指針。
我們可以通過(guò)gnu tool gdb 指令進(jìn)行驗(yàn)證,在觸發(fā)斷點(diǎn)之后通過(guò) info local 命令去查看:
(gdb) info locals b = {_vptr.base = 0x555555755d20 <vtable for base+16>} p = {<base> = {_vptr.base = 0x555555755d00 <vtable for plus+16>}, <No data fields>} p2 = {<base> = {_vptr.base = 0x555555755ce0 <vtable for plus2+16>}, <No data fields>} p3 = {<base> = {_vptr.base = 0x555555755cb8 <vtable for plus3+16>}, <No data fields>} e = {<No data fields>}
我們可以看到每一個(gè)對(duì)象內(nèi)都有一個(gè)指針指向vtable。
當(dāng)一個(gè)基類聲明一個(gè)虛函數(shù)后,在創(chuàng)建對(duì)象的時(shí)候會(huì)將該函數(shù)地址加入虛函數(shù)列表中,如果派生類重寫了該函數(shù),則會(huì)用新函數(shù)地址替換,如果其定義了新函數(shù),則會(huì)將新函數(shù)的指針加入虛表中。
示例代碼如下:
#include <stdio.h> class base { public: base(){}; virtual const char* feature(){return "test";}; virtual void name() {printf("base\n");} virtual ~base() { printf("~base\n"); }; }; class plus : public base { public: plus(/* args */){}; virtual void name() {printf("plus\n");} virtual void parant() {printf("base\n");} ~plus() { printf("plus\n"); }; }; int main() { base b; printf("base: %ld\n", size_t(&b)); plus p; printf("plus: %ld\n", size_t(&p)); }
仍然用 gdb 來(lái)驗(yàn)證,斷點(diǎn)后通過(guò) info vtbl 命令查看:
(gdb) info vtbl p vtable for 'plus' @ 0x555555755d08 (subobject @ 0x7fffffffe010): [0]: 0x555555554b4a <base::feature()> [1]: 0x555555554bf8 <plus::name()> [2]: 0x555555554c30 <plus::~plus()> [3]: 0x555555554c66 <plus::~plus()> [4]: 0x555555554c14 <plus::parant()> (gdb) info vtbl b vtable for 'base' @ 0x555555755d40 (subobject @ 0x7fffffffe008): [0]: 0x555555554b4a <base::feature()> [1]: 0x555555554b5c <base::name()>
當(dāng)調(diào)用虛函數(shù)的時(shí)候,會(huì)在虛函數(shù)表中尋找對(duì)應(yīng)的函數(shù)地址,因此它每一次調(diào)用動(dòng)會(huì)多做一步匹配,相比靜態(tài)聯(lián)編的非虛函數(shù)要更加耗時(shí)。
需要注意的是構(gòu)造函數(shù)不能聲明為虛函數(shù),而如果一個(gè)類作為除非不作為基類,否則建議聲明一個(gè)虛析構(gòu)函數(shù)。
總結(jié)
到此這篇關(guān)于C++ 基礎(chǔ)教程之虛函數(shù)實(shí)例代碼詳解的文章就介紹到這了,更多相關(guān)c++ 虛函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 虛函數(shù)表-C++多態(tài)的實(shí)現(xiàn)原理解析
- C++ 虛函數(shù)和純虛函數(shù)的區(qū)別分析
- 淺談C++ 虛函數(shù)
- 詳解C++純虛函數(shù)與抽象類
- C++ 中virtual 虛函數(shù)用法深入了解
- 詳細(xì)分析C++ 多態(tài)和虛函數(shù)
- 詳解C++虛函數(shù)的工作原理
- C++ 虛函數(shù)專題
- C++中繼承與多態(tài)的基礎(chǔ)虛函數(shù)類詳解
- 如何獲取C++類成員虛函數(shù)地址的示例代碼
- C++ 中const修飾虛函數(shù)實(shí)例詳解
- c++ 虛函數(shù),虛表相關(guān)總結(jié)
相關(guān)文章
C++實(shí)現(xiàn)LeetCode(202.快樂(lè)數(shù))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(202.快樂(lè)數(shù)),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C++中l(wèi)ist的使用方法及常用list操作總結(jié)
這篇文章主要介紹了C++中l(wèi)ist的使用方法及常用list操作總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-06-06詳談C++何時(shí)需要定義賦值/復(fù)制構(gòu)造函數(shù)
下面小編就為大家?guī)?lái)一篇詳談C++何時(shí)需要定義賦值/復(fù)制構(gòu)造函數(shù)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01C++ 基類指針和子類指針相互賦值的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇C++ 基類指針和子類指針相互賦值的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12- 形參出現(xiàn)在函數(shù)定義中,在整個(gè)函數(shù)體內(nèi)都可以使用, 離開(kāi)該函數(shù)則不能使用。實(shí)參出現(xiàn)在主調(diào)函數(shù)中,進(jìn)入被調(diào)函數(shù)后,實(shí)參變量也不能使用,形參和實(shí)參的功能是作數(shù)據(jù)傳送。發(fā)生函數(shù)調(diào)用時(shí), 主調(diào)函數(shù)把實(shí)參的值傳送給被調(diào)函數(shù)的形參從而實(shí)現(xiàn)主調(diào)函數(shù)向被調(diào)函數(shù)的數(shù)據(jù)傳送2021-11-11
C++中標(biāo)準(zhǔn)線程庫(kù)的基本使用介紹
大家好,本篇文章主要講的是C++中標(biāo)準(zhǔn)線程庫(kù)的基本使用介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-02-02