C++多態(tài)的實(shí)現(xiàn)及原理詳細(xì)解析
1. 用virtual關(guān)鍵字申明的函數(shù)叫做虛函數(shù),虛函數(shù)肯定是類的成員函數(shù)。
2. 存在虛函數(shù)的類都有一個(gè)一維的虛函數(shù)表叫做虛表。類的對(duì)象有一個(gè)指向虛表開始的虛指針。虛表是和類對(duì)應(yīng)的,虛表指針是和對(duì)象對(duì)應(yīng)的。
3. 多態(tài)性是一個(gè)接口多種實(shí)現(xiàn),是面向?qū)ο蟮暮诵?。分為類的多態(tài)性和函數(shù)的多態(tài)性。
4. 多態(tài)用虛函數(shù)來(lái)實(shí)現(xiàn),結(jié)合動(dòng)態(tài)綁定。
5. 純虛函數(shù)是虛函數(shù)再加上= 0。
6. 抽象類是指包括至少一個(gè)純虛函數(shù)的類。
純虛函數(shù):virtual void breathe()=0;即抽象類!必須在子類實(shí)現(xiàn)這個(gè)函數(shù)!即先有名稱,沒(méi)內(nèi)容,在派生類實(shí)現(xiàn)內(nèi)容!
我們先看一個(gè)例子:
#include <iostream.h>
class animal
{
public:
void sleep()
{
cout<<"animal sleep"<<endl;
}
void breathe()
{
cout<<"animal breathe"<<endl;
}
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"<<endl;
}
};
void main()
{
fish fh;
animal *pAn=&fh; // 隱式類型轉(zhuǎn)換
pAn->breathe();
}
注意,在例1-1的程序中沒(méi)有定義虛函數(shù)??紤]一下例1-1的程序執(zhí)行的結(jié)果是什么?
答案是輸出:animal breathe
我們?cè)趍ain()函數(shù)中首先定義了一個(gè)fish類的對(duì)象fh,接著定義了一個(gè)指向animal類的指針變量pAn,將fh的地址賦給了指針變量pAn,然后利用該變量調(diào)用pAn->breathe()。許多學(xué)員往往將這種情況和C++的多態(tài)性搞混淆,認(rèn)為fh實(shí)際上是fish類的對(duì)象,應(yīng)該是調(diào)用fish類的breathe(),輸出“fish bubble”,然后結(jié)果卻不是這樣。下面我們從兩個(gè)方面來(lái)講述原因。
1、 編譯的角度
C++編譯器在編譯的時(shí)候,要確定每個(gè)對(duì)象調(diào)用的函數(shù)(要求此函數(shù)是非虛函數(shù))的地址,這稱為早期綁定(early binding),當(dāng)我們將fish類的對(duì)象fh的地址賦給pAn時(shí),C++編譯器進(jìn)行了類型轉(zhuǎn)換,此時(shí)C++編譯器認(rèn)為變量pAn保存的就是animal對(duì)象的地址。當(dāng)在main()函數(shù)中執(zhí)行pAn->breathe()時(shí),調(diào)用的當(dāng)然就是animal對(duì)象的breathe函數(shù)。
2、 內(nèi)存模型的角度
我們給出了fish對(duì)象內(nèi)存模型,如下圖所示:
我們構(gòu)造fish類的對(duì)象時(shí),首先要調(diào)用animal類的構(gòu)造函數(shù)去構(gòu)造animal類的對(duì)象,然后才調(diào)用fish類的構(gòu)造函數(shù)完成自身部分的構(gòu)造,從而拼接出一個(gè)完整的fish對(duì)象。當(dāng)我們將fish類的對(duì)象轉(zhuǎn)換為animal類型時(shí),該對(duì)象就被認(rèn)為是原對(duì)象整個(gè)內(nèi)存模型的上半部分,也就是圖1-1中的“animal的對(duì)象所占內(nèi)存”。那么當(dāng)我們利用類型轉(zhuǎn)換后的對(duì)象指針去調(diào)用它的方法時(shí),當(dāng)然也就是調(diào)用它所在的內(nèi)存中的方法。因此,輸出animal breathe,也就順理成章了。
正如很多學(xué)員所想,在例1-1的程序中,我們知道pAn實(shí)際指向的是fish類的對(duì)象,我們希望輸出的結(jié)果是魚的呼吸方法,即調(diào)用fish類的breathe方法。這個(gè)時(shí)候,就該輪到虛函數(shù)登場(chǎng)了。
前面輸出的結(jié)果是因?yàn)榫幾g器在編譯的時(shí)候,就已經(jīng)確定了對(duì)象調(diào)用的函數(shù)的地址,要解決這個(gè)問(wèn)題就要使用遲綁定(late binding)技術(shù)。當(dāng)編譯器使用遲綁定時(shí),就會(huì)在運(yùn)行時(shí)再去確定對(duì)象的類型以及正確的調(diào)用函數(shù)。而要讓編譯器采用遲綁定,就要在基類中聲明函數(shù)時(shí)使用virtual關(guān)鍵字(注意,這是必須的,很多學(xué)員就是因?yàn)闆](méi)有使用虛函數(shù)而寫出很多錯(cuò)誤的例子),這樣的函數(shù)我們稱為虛函數(shù)。一旦某個(gè)函數(shù)在基類中聲明為virtual,那么在所有的派生類中該函數(shù)都是virtual,而不需要再顯式地聲明為virtual。
下面修改例1-1的代碼,將animal類中的breathe()函數(shù)聲明為virtual,如下:
#include <iostream.h>
class animal
{
public:
void sleep()
{
cout<<"animal sleep"<<endl;
}
virtual void breathe()
{
cout<<"animal breathe"<<endl;
}
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"<<endl;
}
};
void main()
{
fish fh;
animal *pAn=&fh; // 隱式類型轉(zhuǎn)換
pAn->breathe();
}
大家可以再次運(yùn)行這個(gè)程序,你會(huì)發(fā)現(xiàn)結(jié)果是“fish bubble”,也就是根據(jù)對(duì)象的類型調(diào)用了正確的函數(shù)。
那么當(dāng)我們將breathe()聲明為virtual時(shí),在背后發(fā)生了什么呢?
編譯器在編譯的時(shí)候,發(fā)現(xiàn)animal類中有虛函數(shù),此時(shí)編譯器會(huì)為每個(gè)包含虛函數(shù)的類創(chuàng)建一個(gè)虛表(即vtable),該表是一個(gè)一維數(shù)組,在這個(gè)數(shù)組中存放每個(gè)虛函數(shù)的地址。對(duì)于例1-2的程序,animal和fish類都包含了一個(gè)虛函數(shù)breathe(),因此編譯器會(huì)為這兩個(gè)類都建立一個(gè)虛表,(即使子類里面沒(méi)有virtual函數(shù),但是其父類里面有,所以子類中也有了)如下圖所示:
那么如何定位虛表呢?編譯器另外還為每個(gè)類的對(duì)象提供了一個(gè)虛表指針(即vptr),這個(gè)指針指向了對(duì)象所屬類的虛表。在程序運(yùn)行時(shí),根據(jù)對(duì)象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調(diào)用虛函數(shù)時(shí),就能夠找到正確的函數(shù)。對(duì)于例1-2的程序,由于pAn實(shí)際指向的對(duì)象類型是fish,因此vptr指向的fish類的vtable,當(dāng)調(diào)用pAn->breathe()時(shí),根據(jù)虛表中的函數(shù)地址找到的就是fish類的breathe()函數(shù)。
正是由于每個(gè)對(duì)象調(diào)用的虛函數(shù)都是通過(guò)虛表指針來(lái)索引的,也就決定了虛表指針的正確初始化是非常重要的。換句話說(shuō),在虛表指針沒(méi)有正確初始化之前,我們不能夠去調(diào)用虛函數(shù)。那么虛表指針在什么時(shí)候,或者說(shuō)在什么地方初始化呢?
答案是在構(gòu)造函數(shù)中進(jìn)行虛表的創(chuàng)建和虛表指針的初始化。還記得構(gòu)造函數(shù)的調(diào)用順序嗎,在構(gòu)造子類對(duì)象時(shí),要先調(diào)用父類的構(gòu)造函數(shù),此時(shí)編譯器只“看到了”父類,并不知道后面是否后還有繼承者,它初始化父類對(duì)象的虛表指針,該虛表指針指向父類的虛表。當(dāng)執(zhí)行子類的構(gòu)造函數(shù)時(shí),子類對(duì)象的虛表指針被初始化,指向自身的虛表。對(duì)于例2-2的程序來(lái)說(shuō),當(dāng)fish類的fh對(duì)象構(gòu)造完畢后,其內(nèi)部的虛表指針也就被初始化為指向fish類的虛表。在類型轉(zhuǎn)換后,調(diào)用pAn->breathe(),由于pAn實(shí)際指向的是fish類的對(duì)象,該對(duì)象內(nèi)部的虛表指針指向的是fish類的虛表,因此最終調(diào)用的是fish類的breathe()函數(shù)。
要注意:對(duì)于虛函數(shù)調(diào)用來(lái)說(shuō),每一個(gè)對(duì)象內(nèi)部都有一個(gè)虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對(duì)象類型如何轉(zhuǎn)換,但該對(duì)象內(nèi)部的虛表指針是固定的,所以呢,才能實(shí)現(xiàn)動(dòng)態(tài)的對(duì)象函數(shù)調(diào)用,這就是C++多態(tài)性實(shí)現(xiàn)的原理。
總結(jié)(基類有虛函數(shù)):
1. 每一個(gè)類都有虛表。
2. 虛表可以繼承,如果子類沒(méi)有重寫虛函數(shù),那么子類虛表中仍然會(huì)有該函數(shù)的地址,只不過(guò)這個(gè)地址指向的是基類的虛函數(shù)實(shí)現(xiàn)。如果基類有3個(gè)虛函數(shù),那么基類的虛表中就有三項(xiàng)(虛函數(shù)地址),派生類也會(huì)有虛表,至少有三項(xiàng),如果重寫了相應(yīng)的虛函數(shù),那么虛表中的地址就會(huì)改變,指向自身的虛函數(shù)實(shí)現(xiàn)。如果派生類有自己的虛函數(shù),那么虛表中就會(huì)添加該項(xiàng)。
3. 派生類的虛表中虛函數(shù)地址的排列順序和基類的虛表中虛函數(shù)地址排列順序相同。
這就是C++中的多態(tài)性。當(dāng)C++編譯器在編譯的時(shí)候,發(fā)現(xiàn)animal類的breathe()函數(shù)是虛函數(shù),這個(gè)時(shí)候C++就會(huì)采用遲綁定(late binding)技術(shù)。也就是編譯時(shí)并不確定具體調(diào)用的函數(shù),而是在運(yùn)行時(shí),依據(jù)對(duì)象的類型(在程序中,我們傳遞的fish類對(duì)象的地址)來(lái)確認(rèn)調(diào)用的是哪一個(gè)函數(shù),這種能力就叫做C++的多態(tài)性。我們沒(méi)有在breathe()函數(shù)前加virtual關(guān)鍵字時(shí),C++編譯器在編譯時(shí)就確定了哪個(gè)函數(shù)被調(diào)用,這叫做早期綁定(early binding)。
C++的多態(tài)性是通過(guò)遲綁定技術(shù)來(lái)實(shí)現(xiàn)的。
C++的多態(tài)性用一句話概括就是:在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時(shí)將會(huì)根據(jù)對(duì)象的實(shí)際類型來(lái)調(diào)用相應(yīng)的函數(shù)。如果對(duì)象類型是派生類,就調(diào)用派生類的函數(shù);如果對(duì)象類型是基類,就調(diào)用基類的函數(shù)。
虛函數(shù)是在基類中定義的,目的是不確定它的派生類的具體行為。例:
定義一個(gè)基類:class Animal//動(dòng)物。它的函數(shù)為breathe()//呼吸。
再定義一個(gè)類class Fish//魚 。它的函數(shù)也為breathe()
再定義一個(gè)類class Sheep //羊。它的函數(shù)也為breathe()
為了簡(jiǎn)化代碼,將Fish,Sheep定義成基類Animal的派生類。
然而Fish與Sheep的breathe不一樣,一個(gè)是在水中通過(guò)水來(lái)呼吸,一個(gè)是直接呼吸空氣。所以基類不能確定該如何定義breathe,所以在基類中只定義了一個(gè)virtual breathe,它是一個(gè)空的虛函數(shù)。具本的函數(shù)在子類中分別定義。程序一般運(yùn)行時(shí),找到類,如果它有基類,再找它的基類,最后運(yùn)行的是基類中的函數(shù),這時(shí),它在基類中找到的是virtual標(biāo)識(shí)的函數(shù),它就會(huì)再回到子類中找同名函數(shù)。派生類也叫子類。基類也叫父類。這就是虛函數(shù)的產(chǎn)生,和類的多態(tài)性(breathe)的體現(xiàn)。
這里的多態(tài)性是指類的多態(tài)性。
函數(shù)的多態(tài)性是指一個(gè)函數(shù)被定義成多個(gè)不同參數(shù)的函數(shù),它們一般被存在頭文件中,當(dāng)你調(diào)用這個(gè)函數(shù),針對(duì)不同的參數(shù),就會(huì)調(diào)用不同的同名函數(shù)。例:Rect()//矩形。它的參數(shù)可以是兩個(gè)坐標(biāo)點(diǎn)(point,point)也可能是四個(gè)坐標(biāo)(x1,y1,x2,y2)這叫函數(shù)的多態(tài)性與函數(shù)的重載。
類的多態(tài)性,是指用虛函數(shù)和延遲綁定來(lái)實(shí)現(xiàn)的。函數(shù)的多態(tài)性是函數(shù)的重載。
一般情況下(沒(méi)有涉及virtual函數(shù)),當(dāng)我們用一個(gè)指針/引用調(diào)用一個(gè)函數(shù)的時(shí)候,被調(diào)用的函數(shù)是取決于這個(gè)指針/引用的類型。即如果這個(gè)指針/引用是基類對(duì)象的指針/引用就調(diào)用基類的方法;如果指針/引用是派生類對(duì)象的指針/引用就調(diào)用派生類的方法,當(dāng)然如果派生類中沒(méi)有此方法,就會(huì)向上到基類里面去尋找相應(yīng)的方法。這些調(diào)用在編譯階段就確定了。
當(dāng)設(shè)計(jì)到多態(tài)性的時(shí)候,采用了虛函數(shù)和動(dòng)態(tài)綁定,此時(shí)的調(diào)用就不會(huì)在編譯時(shí)候確定而是在運(yùn)行時(shí)確定。不在單獨(dú)考慮指針/引用的類型而是看指針/引用的對(duì)象的類型來(lái)判斷函數(shù)的調(diào)用,根據(jù)對(duì)象中虛指針指向的虛表中的函數(shù)的地址來(lái)確定調(diào)用哪個(gè)函數(shù)。
上一篇:函數(shù)外初始化與函數(shù)內(nèi)初始化詳細(xì)解析
欄 目:C語(yǔ)言
下一篇:函數(shù)式宏定義與普通函數(shù)的區(qū)別
本文標(biāo)題:C++多態(tài)的實(shí)現(xiàn)及原理詳細(xì)解析
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/4050.html
您可能感興趣的文章
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)式函數(shù)庫(kù)
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)數(shù)怎么表達(dá)
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎么打出三角函數(shù)的值
- 01-10c語(yǔ)言求1+2+...+n的解決方法
- 01-10求子數(shù)組最大和的解決方法詳解
- 01-10深入理解約瑟夫環(huán)的數(shù)學(xué)優(yōu)化方法
- 01-10深入二叉樹兩個(gè)結(jié)點(diǎn)的最低共同父結(jié)點(diǎn)的詳解
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)- 解析最少換車次數(shù)的問(wèn)題詳解
- 01-10c語(yǔ)言 跳臺(tái)階問(wèn)題的解決方法


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載