




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第C++超全面講解多態(tài)目錄多態(tài)的概念多態(tài)的定義及實(shí)現(xiàn)構(gòu)成條件虛函數(shù)虛函數(shù)的重寫虛函數(shù)重寫的兩個(gè)例外抽象類抽象類的概念接口繼承和實(shí)現(xiàn)繼承多態(tài)的原理虛函數(shù)表多態(tài)的原理
多態(tài)的概念
概念:通俗的來說就是多種形態(tài),具體就是去完成某個(gè)行為,當(dāng)不同類型的對(duì)象去完成同一件事時(shí),產(chǎn)生的動(dòng)作是不一樣的,結(jié)果也是不一樣的。
舉一個(gè)現(xiàn)實(shí)中的例子:買票這個(gè)行為,當(dāng)普通人買票時(shí)是全價(jià);學(xué)生是半價(jià);軍人是不需要排隊(duì)。
多態(tài)也分為兩種:
靜態(tài)的多態(tài):函數(shù)調(diào)用動(dòng)態(tài)的多態(tài):父類指針或引用調(diào)用重寫虛函數(shù)。
這里的靜態(tài)是指在編譯時(shí)實(shí)現(xiàn)多態(tài)的,而動(dòng)態(tài)是在運(yùn)行時(shí)完成的。
多態(tài)的定義及實(shí)現(xiàn)
構(gòu)成條件
多態(tài)一定是建立在繼承上的,那么除了繼承還要兩個(gè)條件:
必須通過基類(父類)的指針或引用調(diào)用函數(shù)被調(diào)用的函數(shù)必須是虛函數(shù),且派生類(子類)必須對(duì)積累的虛函數(shù)進(jìn)行重寫。
虛函數(shù)
概念:被virtual修飾的類成員函數(shù)稱為虛函數(shù)
classPerson
public:
virtualvoidBuyTicket()
cout"全價(jià)票"endl;
};
注意:
只有類的非靜態(tài)成員函數(shù)可以是虛函數(shù)虛函數(shù)這里virtual和虛繼承中用的是同一個(gè)關(guān)鍵字,但是他們之間沒有關(guān)系;虛函數(shù)這里是為了實(shí)現(xiàn)多態(tài);虛繼承是為了解決菱形繼承的數(shù)據(jù)冗余和二義性,它們沒有關(guān)聯(lián)
虛函數(shù)的重寫
概念:派生類(子類)中有一個(gè)跟基類(父類)完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型,函數(shù)名字,參數(shù)列表完全相同),稱子類的虛函數(shù)重寫了基類的虛函數(shù)。
例:
classPerson
public:
virtualvoidBuyTicket()
cout"全價(jià)票"endl;
classStudent:publicPerson
public:
//子類的虛函數(shù)重寫了父類的虛函數(shù)
virtualvoidBuyTicket()
cout"半價(jià)票"endl;
classSoldier:publicPerson
public:
//子類的虛函數(shù)重寫了父類的虛函數(shù)
virtualvoidBuyTicket()
cout"優(yōu)先買票"endl;
//多態(tài)的實(shí)現(xiàn)
voidf(Personp)//這塊的參數(shù)必須是引用或者指針
p.BuyTicket();
intmain()
Personp;
Studentst;
Soldierso;
f(p);
f(st);
f(so);
return0;
}
注意:這里子函數(shù)的虛函數(shù)可以不加virtual,也算完成了重寫,但是父類的虛函數(shù)必須要加,因?yàn)樽宇愂窍壤^承父類的虛函數(shù),繼承下來后就有了virtual屬性了,子類只是重寫這個(gè)virtual函數(shù);除了這個(gè)原因之外,還有一個(gè)原因,如果父類的析構(gòu)函數(shù)加了virtual,子類加不加都一定完成了重寫,就保證了delete時(shí)一定能實(shí)現(xiàn)多態(tài)的正確調(diào)用析構(gòu)函數(shù)。
虛函數(shù)重寫的兩個(gè)例外
1、協(xié)變
概念:派生類重寫基類虛函數(shù)時(shí),與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對(duì)象的指針或者引用,派生類虛函數(shù)返回派生類對(duì)象的指針或者引用時(shí),稱為協(xié)變
例:
classA{};
classB:publicA{};
classPerson
public:
virtualA*f()
returnnewA;
classStudent:publicPerson
public:
virtualB*f()//返回值不同但是構(gòu)成虛函數(shù)重寫
returnnewB;
};
2、析構(gòu)函數(shù)的重寫
如果基類的析構(gòu)函數(shù)為虛函數(shù),此時(shí)派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字,都與基類的析構(gòu)函數(shù)構(gòu)成重寫,雖然基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同,看起來違背了重寫的規(guī)則,其實(shí)不然,這里可以理解為編譯器對(duì)析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor
例:
classPerson{
public:
//建議把父類析構(gòu)函數(shù)定義為虛函數(shù),這樣方便子類的虛函數(shù)重寫父類的虛函數(shù)
virtual~Person(){cout"~Person()"endl;}
classStudent:publicPerson{
public:
virtual~Student(){cout"~Student()"endl;}
//只有派生類Student的析構(gòu)函數(shù)重寫了Person的析構(gòu)函數(shù),下面的delete對(duì)象調(diào)用析構(gòu)函數(shù),才能構(gòu)成多態(tài),才能保證p1和p2指向的對(duì)象正確的調(diào)用析構(gòu)函數(shù)。
intmain()
Person*p1=newPerson;
//這里p2指向的子類對(duì)象,應(yīng)該調(diào)用子類析構(gòu)函數(shù),如果沒有調(diào)用的話,就可能內(nèi)存泄漏
Person*p2=newStudent;
//多態(tài)行為
deletep1;
deletep2;
//只有析構(gòu)函數(shù)重寫了那么這里delete父類指針調(diào)用析構(gòu)函數(shù)才能實(shí)現(xiàn)多態(tài)。
return0;
}
C++11override和finel
從上面可以看出,C++對(duì)函數(shù)重寫的要求比較嚴(yán)格,但是有些情況下由于疏忽,可能會(huì)導(dǎo)致函數(shù)名字母次序?qū)懛炊鵁o法構(gòu)成重載,而這種錯(cuò)誤在編譯期間是不會(huì)報(bào)出的,只有在程序運(yùn)行時(shí)沒有得到預(yù)期結(jié)果才來debug會(huì)得不償失,因此:C++11提供了override和final兩個(gè)關(guān)鍵字,可以幫助用戶檢測是否重寫
final:修飾虛函數(shù),表示該虛函數(shù)不能再被重寫
classCar
public:
virtualvoidDrive()final{}
classBenz:publicCar
public:
//會(huì)在這塊報(bào)錯(cuò),因?yàn)榛惖奶摵瘮?shù)已經(jīng)被final修飾,不能被重寫了
virtualvoidDrive(){cout"Benz-舒適"endl;}
};
override:檢查派生類虛函數(shù)是否重寫了基類某個(gè)虛函數(shù),如果沒有重寫編譯報(bào)錯(cuò)
classCar{
public:
virtualvoidDrive(){}
classBenz:publicCar{
public:
virtualvoidDrive()override{cout"Benz-舒適"endl;}
};
重載、覆蓋(重寫)、隱藏(重定義)的對(duì)比
抽象類
抽象類的概念
純虛函數(shù):在虛函數(shù)的后面加上=0就是純虛函數(shù),有純虛函數(shù)的類就是抽象類,也叫接口類,抽象類無法實(shí)例化對(duì)象。抽象類的子類不重寫父類的虛函數(shù)的話,也是一個(gè)抽象類。
//抽象類的定義
classCar
public:
virtualvoidrun()=0;//不用實(shí)現(xiàn)只寫接口就行。
};
純虛函數(shù)不寫函數(shù)體,并不意味著不能實(shí)現(xiàn),只是我們不寫。因?yàn)閷懗鰜硪矝]有人用。
虛函數(shù)的作用
強(qiáng)制子類重寫虛函數(shù),完成多態(tài)。表示抽象類。
接口繼承和實(shí)現(xiàn)繼承
普通函數(shù)的繼承就是實(shí)現(xiàn)繼承,虛函數(shù)的繼承就是接口繼承。子類繼承了函數(shù)的實(shí)現(xiàn),可以直接使用。虛函數(shù)重寫后只會(huì)繼承接口,重寫實(shí)現(xiàn)。所以如果不用多態(tài),就不要把函數(shù)寫為虛函數(shù)。
純虛函數(shù)就體現(xiàn)了接口函數(shù)。下面我們來實(shí)現(xiàn)一道題,展現(xiàn)一下接口繼承。
classA
public:
virtualvoidfun(intval=0)
cout"A-val="valendl;
voidFun()
fun();
classB:publicA
public:
virtualvoidfun(intval=1)
cout"B-val"valendl;
intmain()
Bb;
A*a=
a-Fun();
return0;
}
結(jié)果打印為:B-val=0
子類對(duì)象切片給父類指針,傳給Fun函數(shù),滿足多態(tài),會(huì)去調(diào)用子類的fun函數(shù),但是子類的虛函數(shù)繼承了父類的接口,所以val是父類的0。
多態(tài)的原理
虛函數(shù)表
classA
public:
virtualvoidfun()
protected:
int_a;
};
sizeof(A)是多少?
打印出來是8。
我們定義了一個(gè)A類型的對(duì)象a,打開調(diào)試窗口,發(fā)現(xiàn)a的內(nèi)容如下
我們發(fā)現(xiàn)出了成員變量_a以外,還多了一個(gè)指針,這個(gè)指針是不準(zhǔn)確的,實(shí)際上應(yīng)該是_vftptr(virtualfunctiontablepointer),虛函數(shù)表指針。在計(jì)算類大小的時(shí)候要加上這個(gè)指針的大小。虛表就是存放虛函數(shù)的地址地方,當(dāng)我們?nèi)フ{(diào)用虛函數(shù),編譯器就會(huì)通過虛表指針去虛表里查找。
classA
public:
voidfun1()
virtualvoidfun2()
intmain()
A*a=nullptr;
a-fun1();//調(diào)用函數(shù),因?yàn)檫@是普通函數(shù)的調(diào)用
a-fun2();//調(diào)用失敗,虛函數(shù)需要對(duì)指針操作,無法操作空指針。
return0;
}
實(shí)現(xiàn)一個(gè)繼承
classA
public:
virtualvoidfun1()
virtualvoidfun2()
classB:publicA
public:
virtualvoidfun1()
virtualvoidfun2()
intmain()
Aa;
Bb;
return0;
}
子類與父類一樣有一個(gè)虛表指針。
子類的虛函數(shù)表一部分繼承自父類。如果重寫了虛函數(shù),那么子類的虛函數(shù)會(huì)在虛表上覆蓋父類的虛函數(shù)。
本質(zhì)上虛函數(shù)表是一個(gè)虛函數(shù)指針數(shù)組,最后一個(gè)元素是nullptr,代表虛表的結(jié)束。所以,如果繼承了虛函數(shù),那么
子類先拷貝一份父類虛表,然后用一個(gè)虛表指針指向這個(gè)虛表。如果有虛函數(shù)重寫,那么在子類的虛表上用子類的虛函數(shù)覆蓋。子類新增的虛函數(shù)按其在子類中的聲明次序增加到子類虛表的最后。
虛函數(shù)表放在內(nèi)存的那個(gè)區(qū),虛函數(shù)又放在哪?
虛函數(shù)與虛函數(shù)表都放在代碼段。
多態(tài)的原理
我們現(xiàn)在來看多態(tài)的原理
classperson
public:
virtualvoidfun()
cout"全價(jià)票"endl;
classstudent:publicperson
public:
virtualvoidfun()
cout"半價(jià)票"endl;
voidbuyticket(person*p)
p-fun();
}
這樣就實(shí)現(xiàn)了不同對(duì)象去調(diào)用同一函數(shù),展現(xiàn)出不同的形態(tài)。滿足多態(tài)的函數(shù)調(diào)用是程序運(yùn)行是去對(duì)象的虛表查找的,而虛表是在編譯時(shí)確定的。普通函數(shù)的調(diào)用是編譯時(shí)就確定的。
動(dòng)態(tài)綁定與靜態(tài)綁定
1.靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài),比如:函數(shù)重載
2.動(dòng)態(tài)綁定又稱后期綁定(晚綁定),是在程序運(yùn)行期間,根據(jù)具體拿到的類型確定程序的具體行為,調(diào)用具體的函數(shù),也稱為動(dòng)態(tài)多態(tài)。我們說的多態(tài)一般是指動(dòng)態(tài)多態(tài)。
這里我附上一個(gè)有意思的問題:
就是在子類已經(jīng)覆蓋了父類的虛函數(shù)的情況下,為什么子類還是可以調(diào)用被覆蓋的父類的虛函數(shù)呢?
#includeiostream
usingnamespacestd;
classBase{
public:
virt
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 肉制品加工企業(yè)的品牌塑造與品牌形象傳播考核試卷
- 貴金屬選礦藥劑的環(huán)保替代品研究考核試卷
- 行政決策中的效率問題與改進(jìn)措施試題及答案
- 金屬加工工藝參數(shù)理解與應(yīng)用考核試卷
- 套題練習(xí)信息系統(tǒng)監(jiān)理師試題及答案
- 軟件測試工程師必考題目及答案
- 網(wǎng)絡(luò)運(yùn)營商服務(wù)質(zhì)量監(jiān)測試題及答案
- 金屬制品生產(chǎn)過程中的生產(chǎn)計(jì)劃與生產(chǎn)控制策略考核試卷
- 花畫工藝品制作與健康生活方式考核試卷
- 道路設(shè)計(jì)中的人性化因素考慮試題及答案
- 理論聯(lián)系實(shí)際談一談你對(duì)量變質(zhì)變規(guī)律的認(rèn)識(shí)(二)
- 2025年船舶駕駛員考試試卷及答案
- 2025版?zhèn)€人借款合同模板下載
- 制造部生產(chǎn)效率提升計(jì)劃
- 寵物丟失諒解協(xié)議書
- 幼兒園中班科學(xué)活動(dòng)公開課《飛機(jī)本領(lǐng)大》課件
- 體育競彩考試題及答案
- 中國日用器皿行業(yè)市場前景預(yù)測及投資價(jià)值評(píng)估分析報(bào)告
- 2022年新高考全國I卷數(shù)學(xué)真題
- 2025中考英語解題技巧專題10.閱讀表達(dá)解題技巧(學(xué)生版+解析)
- 青少年體重健康管理
評(píng)論
0/150
提交評(píng)論