本文轉(zhuǎn)載自:(做了一些改動(dòng))
http://www.cnblogs.com/feixiang927/p/5048564.html
基本概念
多態(tài)性是一個(gè)接口多種實(shí)現(xiàn),分為類的多態(tài)性和函數(shù)多態(tài)性。
函數(shù)的多態(tài)性(重載)是指一個(gè)函數(shù)被定義成多個(gè)不同參數(shù)的函數(shù),它們一般被存在頭文件中,當(dāng)你調(diào)用這個(gè)函數(shù),針對(duì)不同的參數(shù),就會(huì)調(diào)用不同的同名函數(shù)。
類的多態(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ù)。多態(tài)實(shí)現(xiàn)機(jī)制
舉個(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;
pAn->breathe();
}
答案是輸出:animal breathe
結(jié)果分析:
- 從編譯的角度
C++編譯器在編譯的時(shí)候,要確定每個(gè)對(duì)象調(diào)用的函數(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ù)。 -
從內(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,也就順理成章了。
1.png
要想得到我們想要的結(jié)果就要使用虛函數(shù)
前面輸出的結(jié)果是因?yàn)榫幾g器在編譯的時(shí)候,就已經(jīng)確定了對(duì)象調(diào)用的函數(shù)的地址,要解決這個(gè)問題就要使用遲綁定(late binding)技術(shù)。當(dāng)編譯器使用遲綁定時(shí),就會(huì)在運(yùn)行時(shí)再去確定對(duì)象的類型以及正確的調(diào)用函數(shù)。而要讓編譯器采用遲綁定,就要在基類中聲明函數(shù)時(shí)使用virtual關(guān)鍵字,這樣的函數(shù)我們稱為虛函數(shù)。一旦某個(gè)函數(shù)在基類中聲明為virtual,那么在所有的派生類中該函數(shù)都是virtual,而不需要再顯式地聲明為virtual。
下面我們將上面一段代碼進(jìn)行部分修改
virtual void breathe()
{
cout<<"animal breathe"<<endl;
}
運(yùn)行結(jié)果:fish bubble
結(jié)果分析
編譯器為每個(gè)類的對(duì)象提供一個(gè)虛表指針,這個(gè)指針指向?qū)ο笏鶎兕惖奶摫怼T诔绦蜻\(yùn)行時(shí),根據(jù)對(duì)象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調(diào)用虛函數(shù)時(shí),就能夠找到正確的函數(shù)。
由于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ō),在虛表指針沒有正確初始化之前,我們不能夠去調(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ì)象的虛表指針被初始化,指向自身的虛表。
當(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ù)。
在該文章中詳細(xì)講解了繼承中類的內(nèi)存對(duì)象模型:http://www.itdecent.cn/p/31373b52902d
虛函數(shù)與純虛函數(shù)區(qū)別:
- 虛函數(shù)和純虛函數(shù)可以定義在同一個(gè)類(class)中,含有純虛函數(shù)的類被稱為抽象類(abstract class),而只含有虛函數(shù)的類(class)不能被稱為抽象類(abstract class)。
- 虛函數(shù)可以被直接使用,也可以被子類(sub class)重載以后以多態(tài)的形式調(diào)用,而純虛函數(shù)必須在子類(sub class)中實(shí)現(xiàn)該函數(shù)才可以使用,因?yàn)榧兲摵瘮?shù)在基類(base class)只有聲明而沒有定義。
- 虛函數(shù)和純虛函數(shù)都可以在子類(sub class)中被重載,以多態(tài)的形式被調(diào)用。
- 虛函數(shù)和純虛函數(shù)通常存在于抽象基類(abstract base class -ABC)之中,被繼承的子類重載,目的是提供一個(gè)統(tǒng)一的接口。
- 虛函數(shù)的定義形式:virtual {method body}
純虛函數(shù)的定義形式:virtual { } = 0;
在虛函數(shù)和純虛函數(shù)的定義中不能有static標(biāo)識(shí)符,原因很簡(jiǎn)單,被static修飾的函數(shù)在編譯時(shí)候要求前期bind,然而虛函數(shù)卻是動(dòng)態(tài)綁定(run-time bind),而且被兩者修飾的函數(shù)生命周期(life recycle)也不一樣。 - 虛函數(shù)必須實(shí)現(xiàn),如果不實(shí)現(xiàn),編譯器將報(bào)錯(cuò),錯(cuò)誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)" - 對(duì)于虛函數(shù)來(lái)說(shuō),父類和子類都有各自的版本。由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定。
- 實(shí)現(xiàn)了純虛函數(shù)的子類,該純虛函數(shù)在子類中就變成了虛函數(shù),子類的子類即孫子類可以覆蓋該虛函數(shù),由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定。
- 虛函數(shù)是C++中用于實(shí)現(xiàn)多態(tài)(polymorphism)的機(jī)制。核心理念就是通過(guò)基類訪問派生類定義的函數(shù)。
- 多態(tài)性指相同對(duì)象收到不同消息或不同對(duì)象收到相同消息時(shí)產(chǎn)生不同的實(shí)現(xiàn)動(dòng)作。C++支持兩種多態(tài)性:編譯時(shí)多態(tài)性,運(yùn)行時(shí)多態(tài)性。
a.編譯時(shí)多態(tài)性:通過(guò)重載函數(shù)實(shí)現(xiàn)
b 運(yùn)行時(shí)多態(tài)性:通過(guò)虛函數(shù)實(shí)現(xiàn) - 如果一個(gè)類中含有純虛函數(shù),那么任何試圖對(duì)該類進(jìn)行實(shí)例化的語(yǔ)句都將導(dǎo)致錯(cuò)誤的產(chǎn)生,因?yàn)槌橄蠡?ABC)是不能被直接調(diào)用的。必須被子類繼承重載以后,根據(jù)要求調(diào)用其子類的方法。
