計(jì)算機(jī)程序基礎(chǔ)教程(13):C++ 繼承與虛函數(shù)
【繼承】
繼承可以讓兩個(gè)類共同使用一部分非私有成員,提供共用成員的類稱為父類或基類,使用共用成員的類稱為子類或派生類,子類創(chuàng)建對(duì)象時(shí)會(huì)包含繼承自父類的成員。
?● 繼承方式
繼承方式用于設(shè)置子類繼承自父類成員的訪問(wèn)權(quán)限。
public,子類繼承的父類成員保持原有訪問(wèn)權(quán)限,最常用的是此方式。
private,子類繼承的父類成員全部改為私有權(quán)限。
protected,子類繼承的父類成員全部改為保護(hù)權(quán)限。
?● 子類創(chuàng)建對(duì)象
子類創(chuàng)建對(duì)象時(shí),會(huì)自動(dòng)創(chuàng)建父類對(duì)象,并在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)執(zhí)行,若子類沒(méi)有構(gòu)造函數(shù)則編譯器自動(dòng)為其創(chuàng)建一個(gè)構(gòu)造函數(shù),作用是調(diào)用父類構(gòu)造函數(shù)執(zhí)行,若父類構(gòu)造函數(shù)有參數(shù),則應(yīng)該在子類中自定義構(gòu)造函數(shù),并為父類構(gòu)造函數(shù)參數(shù)賦值。
子類構(gòu)造函數(shù)會(huì)首先調(diào)用父類構(gòu)造函數(shù)執(zhí)行,之后再返回執(zhí)行子類構(gòu)造函數(shù)代碼,若兩個(gè)構(gòu)造函數(shù)功能有沖突,則以子類構(gòu)造函數(shù)為準(zhǔn)。
另外子類創(chuàng)建對(duì)象時(shí)不能直接賦值,這樣容易與父類構(gòu)造函數(shù)產(chǎn)生沖突。
子類對(duì)象使用完畢后,子類析構(gòu)函數(shù)會(huì)調(diào)用父類析構(gòu)函數(shù)執(zhí)行,執(zhí)行順序?yàn)槭紫葓?zhí)行子類析構(gòu)函數(shù)、之后執(zhí)行父類析構(gòu)函數(shù),若子類沒(méi)有析構(gòu)函數(shù)則編譯器自動(dòng)創(chuàng)建一個(gè)析構(gòu)函數(shù),作用是調(diào)用父類析構(gòu)函數(shù)執(zhí)行。
?● 對(duì)象類型轉(zhuǎn)換
對(duì)象有類型之分,子類對(duì)象可以轉(zhuǎn)換為父類類型,轉(zhuǎn)換之后的子類對(duì)象只能調(diào)用繼承自父類的成員,不能調(diào)用子類自定義成員,而父類類型不能轉(zhuǎn)換為子類類型。
父類對(duì)象可以使用子類對(duì)象賦值,編譯器自動(dòng)將子類對(duì)象轉(zhuǎn)換為父類類型。
子類對(duì)象不能使用父類對(duì)象賦值,父類中不包含子類自定義的成員。
【多繼承】
繼承關(guān)系可以一直傳遞下去,比如A派生B,B派生C,那C也會(huì)間接繼承A,創(chuàng)建C對(duì)象時(shí),會(huì)自動(dòng)創(chuàng)建A、B對(duì)象,C調(diào)用B構(gòu)造函數(shù),B調(diào)用A構(gòu)造函數(shù)。
除此之外,多繼承還有另一種形式,一個(gè)類可以直接繼承多個(gè)類,每個(gè)繼承都可以單獨(dú)設(shè)置繼承方式,多個(gè)父類的構(gòu)造函數(shù)都由子類構(gòu)造函數(shù)負(fù)責(zé)調(diào)用。
?● 菱形繼承
菱形繼承是一種復(fù)雜的多繼承,繼承關(guān)系圖組成一個(gè)菱形,具體方式為:A派生出B和C,D又同時(shí)繼承B和C,D創(chuàng)建對(duì)象時(shí)會(huì)自動(dòng)創(chuàng)建B、C對(duì)象,B、C又會(huì)各自創(chuàng)建一個(gè)A對(duì)象,此時(shí)D對(duì)象就有兩個(gè)可以使用的A對(duì)象,這將會(huì)導(dǎo)致混亂。
為了解決菱形繼承的混亂,C++規(guī)定在菱形繼承關(guān)系中,B和C繼承A時(shí)添加virtual關(guān)鍵字設(shè)置為虛繼承,此時(shí)創(chuàng)建D對(duì)象時(shí)只會(huì)創(chuàng)建一個(gè)A對(duì)象,并且三個(gè)父類對(duì)象的構(gòu)造函數(shù)都將由D負(fù)責(zé)調(diào)用,而單獨(dú)創(chuàng)建B或C對(duì)象時(shí),與普通繼承方式相同,沒(méi)有區(qū)別。
【虛函數(shù)】
虛函數(shù)的作用是通過(guò)指針調(diào)用成員函數(shù),雖然普通成員函數(shù)也可以使用指針調(diào)用,但是有一些限制,比如只能調(diào)用本類中的成員函數(shù)(包含繼承自父類的函數(shù)),而使用虛函數(shù)可以通過(guò)指針調(diào)用子類的成員函數(shù),從而擴(kuò)大函數(shù)指針的使用范圍,同源繼承關(guān)系中所有類的虛函數(shù)都可以通過(guò)函數(shù)指針調(diào)用。
虛函數(shù)在父類中定義,子類繼承虛函數(shù)后可以直接使用,也可以重寫(xiě)虛函數(shù)內(nèi)部代碼,重寫(xiě)虛函數(shù)類似在子類中定義同名函數(shù),并且參數(shù)、返回值也相同。
編譯器會(huì)將每個(gè)類的虛函數(shù)的地址放在一個(gè)數(shù)組中,稱為虛函數(shù)地址表,函數(shù)指針使用此表進(jìn)行賦值。
虛函數(shù)使用父類對(duì)象指針調(diào)用,編譯器會(huì)轉(zhuǎn)換為使用函數(shù)指針調(diào)用虛函數(shù),對(duì)象指針賦值為哪個(gè)類對(duì)象的地址,就調(diào)用哪個(gè)類定義的虛函數(shù)(若子類沒(méi)有重寫(xiě)虛函數(shù),則調(diào)用父類虛函數(shù)),并且使用此對(duì)象指針為執(zhí)行虛函數(shù)的this參數(shù)賦值。
虛函數(shù)也可以當(dāng)做普通函數(shù)使用,直接通過(guò)函數(shù)名調(diào)用它,此時(shí)等于不使用虛函數(shù)機(jī)制,與使用普通函數(shù)無(wú)異。
父類中定義有虛函數(shù)時(shí),必須定義一個(gè)虛析構(gòu)函數(shù),作用是調(diào)用使用new申請(qǐng)內(nèi)存存儲(chǔ)的子類對(duì)象析構(gòu)函數(shù)執(zhí)行,比如上面代碼中父類對(duì)象指針這樣賦值:base * a = new k;,子類對(duì)象使用完畢后執(zhí)行delete a刪除,指針a是父類類型,此時(shí)編譯器默認(rèn)只調(diào)用父類析構(gòu)函數(shù),不會(huì)調(diào)用子類析構(gòu)函數(shù),若將父類析構(gòu)函數(shù)定義為虛析構(gòu)函數(shù),則會(huì)調(diào)用子類析構(gòu)函數(shù)。
?● 純虛函數(shù)
使用虛函數(shù)機(jī)制時(shí),需要?jiǎng)?chuàng)建父類對(duì)象指針,作用是通過(guò)函數(shù)指針調(diào)用子類虛函數(shù)執(zhí)行,但是父類本身并不一定需要?jiǎng)?chuàng)建對(duì)象,若父類無(wú)需創(chuàng)建對(duì)象,只是提供父類對(duì)象指針,滿足虛函數(shù)機(jī)制的使用方式,可以將父類中的虛函數(shù)定義為純虛函數(shù),包含純虛函數(shù)的類稱為抽象類。
純虛函數(shù)與虛函數(shù)的主要區(qū)別如下:
包含純虛函數(shù)的抽象類不能創(chuàng)建對(duì)象。
純虛函數(shù)沒(méi)有內(nèi)容,子類必須重寫(xiě)純虛函數(shù),否則子類也是抽象類。