c++多態(tài)(簡(jiǎn)潔實(shí)例)

一:基本定義

多態(tài)按字面的意思就是多種形態(tài)瘦陈。當(dāng)類之間存在層次結(jié)構(gòu)踪区,并且類之間是通過繼承關(guān)聯(lián)時(shí)铺韧,就會(huì)用到多態(tài)。

C++ 多態(tài)意味著調(diào)用成員函數(shù)時(shí)蟹略,會(huì)根據(jù)調(diào)用函數(shù)的對(duì)象的類型來執(zhí)行不同的函數(shù)登失。

下面的實(shí)例中,基類 Shape 被派生為兩個(gè)類挖炬,如下所示:

#include

using namespace std;

class Shape {

protected:

int width, height;

public:

Shape( int a=0, int b=0)

{

width = a;

height = b;

}

int area()

{

cout << "Parent class area :" <

return 0;

}

};

class Rectangle: public Shape{

public:

Rectangle( int a=0, int b=0):Shape(a, b) { }

int area ()

{

cout << "Rectangle class area :" <

return (width * height);

}

};

class Triangle: public Shape{

public:

Triangle( int a=0, int b=0):Shape(a, b) { }

int area ()

{

cout << "Triangle class area :" <

return (width * height / 2);

}

};

// 程序的主函數(shù)

int main( )

{

Shape *shape;

Rectangle rec(10,7);

Triangle? tri(10,5);

// 存儲(chǔ)矩形的地址

shape = &rec;

// 調(diào)用矩形的求面積函數(shù) area

shape->area();

// 存儲(chǔ)三角形的地址

shape = &tri;

// 調(diào)用三角形的求面積函數(shù) area

shape->area();

return 0;

}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí)揽浙,它會(huì)產(chǎn)生下列結(jié)果:

Parent class area

Parent class area

導(dǎo)致錯(cuò)誤輸出的原因是,調(diào)用函數(shù) area() 被編譯器設(shè)置為基類中的版本茅茂,這就是所謂的靜態(tài)多態(tài)捏萍,或靜態(tài)鏈接 - 函數(shù)調(diào)用在程序執(zhí)行前就準(zhǔn)備好了。有時(shí)候這也被稱為早綁定空闲,因?yàn)?area() 函數(shù)在程序編譯期間就已經(jīng)設(shè)置好了令杈。

但現(xiàn)在,讓我們對(duì)程序稍作修改碴倾,在 Shape 類中逗噩,area() 的聲明前放置關(guān)鍵字 virtual,如下所示:

class Shape {

protected:

int width, height;

public:

Shape( int a=0, int b=0)

{

width = a;

height = b;

}

virtual int area()

{

cout << "Parent class area :" <

return 0;

}

};

修改后跌榔,當(dāng)編譯和執(zhí)行前面的實(shí)例代碼時(shí)异雁,它會(huì)產(chǎn)生以下結(jié)果:

Rectangle class area

Triangle class area

此時(shí),編譯器看的是指針的內(nèi)容僧须,而不是它的類型纲刀。因此,由于 tri 和 rec 類的對(duì)象的地址存儲(chǔ)在 *shape 中担平,所以會(huì)調(diào)用各自的 area() 函數(shù)示绊。

正如您所看到的,每個(gè)子類都有一個(gè)函數(shù) area() 的獨(dú)立實(shí)現(xiàn)暂论。這就是多態(tài)的一般使用方式面褐。有了多態(tài),您可以有多個(gè)不同的類取胎,都帶有同一個(gè)名稱但具有不同實(shí)現(xiàn)的函數(shù)展哭,函數(shù)的參數(shù)甚至可以是相同的。


二:虛函數(shù)

虛函數(shù) 是在基類中使用關(guān)鍵字 virtual 聲明的函數(shù)闻蛀。在派生類中重新定義基類中定義的虛函數(shù)時(shí)匪傍,會(huì)告訴編譯器不要靜態(tài)鏈接到該函數(shù)。

我們想要的是在程序中任意點(diǎn)可以根據(jù)所調(diào)用的對(duì)象類型來選擇調(diào)用的函數(shù)觉痛,這種操作被稱為動(dòng)態(tài)鏈接役衡,或后期綁定。


三:純虛函數(shù)

您可能想要在基類中定義虛函數(shù)秧饮,以便在派生類中重新定義該函數(shù)更好地適用于對(duì)象映挂,但是您在基類中又不能對(duì)虛函數(shù)給出有意義的實(shí)現(xiàn),這個(gè)時(shí)候就會(huì)用到純虛函數(shù)盗尸。

我們可以把基類中的虛函數(shù) area() 改寫如下:

class Shape {

protected:

int width, height;

public:

Shape( int a=0, int b=0)

{

width = a;

height = b;

}

// pure virtual function

virtual int area() = 0;

};

= 0 告訴編譯器柑船,函數(shù)沒有主體,上面的虛函數(shù)是純虛函數(shù)泼各。


四:注意事項(xiàng)

1鞍时、純虛函數(shù)聲明如下: virtual void funtion1()=0; 純虛函數(shù)一定沒有定義,純虛函數(shù)用來規(guī)范派生類的行為扣蜻,即接口逆巍。包含純虛函數(shù)的類是抽象類,抽象類不能定義實(shí)例莽使,但可以聲明指向?qū)崿F(xiàn)該抽象類的具體類的指針或引用锐极。

2、虛函數(shù)聲明如下:virtual ReturnType FunctionName(Parameter) 虛函數(shù)必須實(shí)現(xiàn)芳肌,如果不實(shí)現(xiàn)灵再,編譯器將報(bào)錯(cuò),錯(cuò)誤提示為:

error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"

3亿笤、對(duì)于虛函數(shù)來說翎迁,父類和子類都有各自的版本。由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定净薛。

4汪榔、實(shí)現(xiàn)了純虛函數(shù)的子類,該純虛函數(shù)在子類中就編程了虛函數(shù)肃拜,子類的子類即孫子類可以覆蓋該虛函數(shù)痴腌,由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定。

5爆班、虛函數(shù)是C++中用于實(shí)現(xiàn)多態(tài)(polymorphism)的機(jī)制衷掷。核心理念就是通過基類訪問派生類定義的函數(shù)。

6柿菩、在有動(dòng)態(tài)分配堆上內(nèi)存的時(shí)候戚嗅,析構(gòu)函數(shù)必須是虛函數(shù),但沒有必要是純虛的枢舶。

7懦胞、友元不是成員函數(shù),只有成員函數(shù)才可以是虛擬的凉泄,因此友元不能是虛擬函數(shù)躏尉。但可以通過讓友元函數(shù)調(diào)用虛擬成員函數(shù)來解決友元的虛擬問題。

8后众、析構(gòu)函數(shù)應(yīng)當(dāng)是虛函數(shù)胀糜,將調(diào)用相應(yīng)對(duì)象類型的析構(gòu)函數(shù)颅拦,因此,如果指針指向的是子類對(duì)象教藻,將調(diào)用子類的析構(gòu)函數(shù)距帅,然后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)。


C++多態(tài)意味著調(diào)用成員函數(shù)時(shí)括堤,會(huì)根據(jù)調(diào)用函數(shù)的對(duì)象的類型來執(zhí)行不同的函數(shù)碌秸;

形成多態(tài)必須具備三個(gè)條件:

1、必須存在繼承關(guān)系悄窃;

2讥电、繼承關(guān)系必須有同名虛函數(shù)(其中虛函數(shù)是在基類中使用關(guān)鍵字Virtual聲明的函數(shù),在派生類中重新定義基類中定義的虛函數(shù)時(shí)轧抗,會(huì)告訴編譯器不要靜態(tài)鏈接到該函數(shù))恩敌;

3、存在基類類型的指針或者引用横媚,通過該指針或引用調(diào)用虛函數(shù)潮剪;


五:動(dòng)態(tài)聯(lián)編的實(shí)現(xiàn)機(jī)制 VTABLE

編譯器對(duì)每個(gè)包含虛函數(shù)的類創(chuàng)建一個(gè)虛函數(shù)表VTABLE,表中每一項(xiàng)指向一個(gè)虛函數(shù)的地址分唾,即VTABLE表可以看成一個(gè)函數(shù)指針的數(shù)組抗碰,每個(gè)虛函數(shù)的入口地址就是這個(gè)數(shù)組的一個(gè)元素。

每個(gè)含有虛函數(shù)的類都有各自的一張?zhí)摵瘮?shù)表VTABLE绽乔。每個(gè)派生類的VTABLE繼承了它各個(gè)基類的VTABLE弧蝇,如果基類VTABLE中包含某一項(xiàng)(虛函數(shù)的入口地址),則其派生類的VTABLE中也將包含同樣的一項(xiàng)折砸,但是兩項(xiàng)的值可能不同看疗。如果派生類中重載了該項(xiàng)對(duì)應(yīng)的虛函數(shù),則派生類VTABLE的該項(xiàng)指向重載后的虛函數(shù)睦授,如果派生類中沒有對(duì)該項(xiàng)對(duì)應(yīng)的虛函數(shù)進(jìn)行重新定義两芳,則使用基類的這個(gè)虛函數(shù)地址。

在創(chuàng)建含有虛函數(shù)的類的對(duì)象的時(shí)候去枷,編譯器會(huì)在每個(gè)對(duì)象的內(nèi)存布局中增加一個(gè)vptr指針項(xiàng)怖辆,該指針指向本類的VTABLE。在通過指向基類對(duì)象的指針(設(shè)為bp)調(diào)用一個(gè)虛函數(shù)時(shí)删顶,編譯器生成的代碼是先獲取所指對(duì)象的vtb1指針竖螃,然后調(diào)用vtb1所指向類的VTABLE中的對(duì)應(yīng)項(xiàng)(具體虛函數(shù)的入口地址)。

當(dāng)基類中沒有定義虛函數(shù)時(shí),其長(zhǎng)度=數(shù)據(jù)成員長(zhǎng)度;派生類長(zhǎng)度=自身數(shù)據(jù)成員長(zhǎng)度+基類繼承的數(shù)據(jù)成員長(zhǎng)度患民;

當(dāng)基類中定義虛函數(shù)后掘剪,其長(zhǎng)度=數(shù)據(jù)成員長(zhǎng)度+虛函數(shù)表的地址長(zhǎng)度腻格;派生類長(zhǎng)度=自身數(shù)據(jù)成員長(zhǎng)度+基類繼承的數(shù)據(jù)成員長(zhǎng)度+虛函數(shù)表的地址長(zhǎng)度画拾。

包含一個(gè)虛函數(shù)和幾個(gè)虛函數(shù)的類的長(zhǎng)度增量為0。含有虛函數(shù)的類只是增加了一個(gè)指針用于存儲(chǔ)虛函數(shù)表的首地址菜职。

派生類與基類同名的虛函數(shù)在VTABLE中有相同的索引號(hào)(或序號(hào))碾阁。

虛函數(shù)這里說的有些亂,因?yàn)?C++ 寫法奇葩略多些楣。其實(shí)可以簡(jiǎn)單理解。

虛函數(shù)可以不實(shí)現(xiàn)(定義)宪睹。不實(shí)現(xiàn)(定義)的虛函數(shù)是純虛函數(shù)愁茁。

在一個(gè)類中如果存在未定義的虛函數(shù),那么不能直接使用該類的實(shí)例亭病,可以理解因?yàn)槲炊x virtual 函數(shù)鹅很,其類是抽象的,無法實(shí)例化罪帖。將報(bào)錯(cuò)誤:

undefined reference to `vtable for xxx'

這和其它語(yǔ)言的抽象類促煮,抽象方法是類似的——我們必須實(shí)現(xiàn)抽象類,否則無法實(shí)例化整袁。(virtual 和 abstract還是有些區(qū)別的)

也就是說菠齿,如果存在以下代碼:

using namespace std;

class Base {

public:

virtual void tall();

};

class People : Base {

public:

void tall() {

cout << "people" << endl;

};

};


那么,在 main 方法中坐昙,我們不能使用 Base base; 這行代碼绳匀,此時(shí)的 tall 沒有實(shí)現(xiàn),函數(shù)表(vtable)的引用是未定義的炸客,故而無法執(zhí)行疾棵。但我們可以使用 People people; 然后 people.tall(); 或 (&people)->tall(); 因?yàn)镻eople實(shí)現(xiàn)或者說重寫、覆蓋了 Base 的純虛方法 tall()痹仙,使其在 People 類中有了定義是尔,函數(shù)表掛上去了,于是可以誕生實(shí)例了开仰。

int main() {

//??? Base base;//不可用

People people;//可用

people.tall();

(&people)->tall();

return 0;

}

上述的是針對(duì)虛函數(shù)而言拟枚,普通的函數(shù),即使我們只聲明众弓,不定義梨州,也不會(huì)產(chǎn)生上述不可用的問題。

六:其他虛函數(shù)注意事項(xiàng)

父類的虛函數(shù)或純虛函數(shù)在子類中依然是虛函數(shù)田轧。有時(shí)我們并不希望父類的某個(gè)函數(shù)在子類中被重寫暴匠,在 C++11 及以后可以用關(guān)鍵字 final 來避免該函數(shù)再次被重寫。

例:

#include

using namespace std;

class Base

{

public:

virtual void func()

{

cout<<"This is Base"<

}

};

class _Base:public Base

{

public:

void func() final//正確傻粘,func在Base中是虛函數(shù)

{

cout<<"This is _Base"<

}

};

class __Base:public _Base

{

/*??? public://不正確每窖,func在_Base中已經(jīng)不再是虛函數(shù)帮掉,不能再被重寫

void func()

{

cout<<"This is __Base"<

}*/

};

int main()

{

_Base a;

__Base b;

Base* ptr=&a;

ptr->func();

ptr=&b;

_Base* ptr2=&b;??? ptr->func();

ptr2->func();

}


以上程序運(yùn)行結(jié)果:

This is _Base

This is _Base

This is _Base

如果不希望一個(gè)類被繼承,也可以使用 final 關(guān)鍵字窒典。

格式如下:

class Class_name final

{

...

};

則該類將不能被繼承蟆炊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瀑志,隨后出現(xiàn)的幾起案子涩搓,更是在濱河造成了極大的恐慌,老刑警劉巖劈猪,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昧甘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡战得,警方通過查閱死者的電腦和手機(jī)充边,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來常侦,“玉大人浇冰,你說我怎么就攤上這事×觯” “怎么了肘习?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)坡倔。 經(jīng)常有香客問我井厌,道長(zhǎng),這世上最難降的妖魔是什么致讥? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任仅仆,我火速辦了婚禮,結(jié)果婚禮上垢袱,老公的妹妹穿的比我還像新娘墓拜。我一直安慰自己,他們只是感情好请契,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布咳榜。 她就那樣靜靜地躺著,像睡著了一般爽锥。 火紅的嫁衣襯著肌膚如雪涌韩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天氯夷,我揣著相機(jī)與錄音臣樱,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雇毫,可吹牛的內(nèi)容都是我干的玄捕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棚放,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼枚粘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起飘蚯,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馍迄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后局骤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攀圈,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年庄涡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搬设。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡穴店,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拿穴,到底是詐尸還是另有隱情泣洞,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布默色,位于F島的核電站球凰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏腿宰。R本人自食惡果不足惜呕诉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吃度。 院中可真熱鬧甩挫,春花似錦、人聲如沸椿每。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)间护。三九已至亦渗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汁尺,已是汗流浹背法精。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亿虽。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓菱涤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親洛勉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粘秆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容