版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第9章繼承9.1繼承與組合9.2繼承方式9.3派生類的構(gòu)造與析構(gòu)9.4派生類的使用本章小結(jié)習(xí)題
C++最重要的特征之一是代碼重用,但是如果希望更進(jìn)一步,則不僅僅是要拷貝代碼和修改代碼,而是要做更多的工作。在C++中可重用性是通過繼承(inheritance)這一機(jī)制來實(shí)現(xiàn)的。繼承不僅在很大程度上提高了代碼的可重用性和維護(hù)上的方便性,而且是面向?qū)ο缶幊痰暮诵乃枷搿S袝r(shí)一個(gè)類的數(shù)據(jù)成員和成員函數(shù)與原有的類基本相同或者接近,就可以利用原有的類加上新的內(nèi)容來實(shí)現(xiàn),以減少重復(fù)的工作。這就是C++的繼承機(jī)制。本章將介紹繼承在C++這門面向?qū)ο蟮恼Z言中所體現(xiàn)出來的核心作用,利用繼承的機(jī)制在已存在的類基礎(chǔ)上構(gòu)造一個(gè)新類。已存在的類稱為“基類”(baseclass)或“父類”(fatherclass)。新建立的類稱為“派生類”(derivedclass)或“子類”(sonclass)。9.1繼承與組合9.1.1繼承的概念與語法圖9.1呈現(xiàn)了生活中比較常見的交通工具的類層次。最頂部的“交通工具”可稱為“基類”或“父類”,這個(gè)基類有三個(gè)子類(也可以稱為“派生類”),分別為“空中交通工具”、“陸地交通工具”和“水上交通工具”,稱這些子類是從基類繼承而來的。而其中“陸地交通工具”還有它自己的子類,這就涉及到了多級繼承的問題。在這個(gè)結(jié)構(gòu)圖中,有四層類結(jié)構(gòu),其中包含了三層繼承關(guān)系。這里,每個(gè)子類都是其基類的特定化版本。圖9.1交通工具的類層次
通過圖9.1這個(gè)例子可以看出:通過繼承,可以用一種既簡單又形象的方式來描述一種事物。比如,如果要描述什么叫狗,就可以說:它是一種會(huì)“汪汪”叫的哺乳動(dòng)物。在這個(gè)例子中,狗就是哺乳動(dòng)物的子類。換種說法,狗是哺乳動(dòng)物的一種,而且狗又同時(shí)具備它自己獨(dú)有的特征,就是會(huì)“汪汪”叫,這個(gè)狗獨(dú)有的特性,是區(qū)別于其他哺乳動(dòng)物的屬性。由于哺乳動(dòng)物有很多的共性,因此在采取這種方式來描述狗時(shí),只需將其所有的特性描述清楚就可以了。由此可以看出,繼承使得我們描述某種事物的能力大大增強(qiáng),而且簡單、形象。繼承是C++?語言的一種重要機(jī)制,該機(jī)制自動(dòng)地為一個(gè)新類提供來自于另一個(gè)已存在的類的操作和其數(shù)據(jù)結(jié)構(gòu)等,這樣只需要在新類中定義已存在的類中沒有的內(nèi)容來構(gòu)建這個(gè)新類。
繼承的語法形式如下:
classB:繼承方式類A{ //私有成員
private: //公有成員
public: //保護(hù)成員
protected: …};
在這個(gè)例子中所提到的繼承方式(public、private、protected)在第2章的2.3節(jié)已經(jīng)有過介紹。在繼承中,繼承方式可以控制子類允許從基類中所能繼承的成員內(nèi)容。而繼承方式對繼承的類的控制方式就類似于在前面介紹的對于在一個(gè)類中的成員的存取控制。換句話說,繼承方式規(guī)定了子類對基類成員的訪問權(quán)限。關(guān)于這一知識(shí)點(diǎn)的內(nèi)容,將在下一節(jié)詳細(xì)介紹。需要注意的是,不論繼承的方式是公有繼承、私有繼承還是保護(hù)繼承,基類中的private屬性都無法被子類進(jìn)行訪問。
【程序9.1】#include<iostream>usingnamespacestd;classA{private:inta;protected: intb;public: intc; A(){a=1;b=2;c=3;} voidsetA(inta){this->a=a;} voidsetB(intb){this->b=b;} voidsetC(intc){this->c=c;} voidprint(){cout<<"a="<<a<<endl<<"b="<<b<<endl<<"c="<<c<<endl;}};classB:protectedA{public: B():A(){} voidoutput(){cout<<"b="<<b<<endl;} //可以在公有、保護(hù)繼承中使用
};voidmain(){Aoa; cout<<"A::a="<<*(int*)(&oa)<<endl; cout<<"A::b="<<*((int*)(&oa)+1)<<endl; cout<<"A::c="<<*((int*)(&oa)+2)<<endl; oa.print();
Bob; cout<<"B::a="<<*(int*)(&ob)<<endl; cout<<"B::b="<<*((int*)(&ob)+1)<<endl;cout<<"B::c="<<*((int*)(&ob)+2)<<endl; //ob.print(); //可以在公有繼承中使用
ob.output(); //可以在公有、保護(hù)繼承中使用
cout<<"sizeofA="<<sizeof(A)<<endl <<"sizeofB="<<sizeof(B)<<endl;//A和B具有相同的結(jié)構(gòu)大小
}
運(yùn)行結(jié)果如下:
從程序9.1中可以看出,子類還是繼承了基類的屬性,只是對基類中的私有成員沒有訪問權(quán)限。這是因?yàn)樗接谐蓡T作為某一個(gè)類的私有內(nèi)容,不會(huì)因?yàn)槠渌蚓透倪@種私有關(guān)系,除了該類自身的成員或該類的友元(見2.4節(jié))外,其他任何類,即使是它的派生類,也無法對它的私有成員進(jìn)行訪問,而且派生類也可以有自己的私有成員或者其他增加的部分。類繼承如圖9.2所示。從圖9.2中可以看出,類B從類A的繼承為程序員提供了代碼的可重用性,而子類自身所增加的內(nèi)容則在源代碼的基礎(chǔ)上進(jìn)行了擴(kuò)充和改進(jìn)。圖9.2類繼承
在C++中,繼承這種結(jié)構(gòu)關(guān)系可以有很多級,而且具有單向和傳遞性。舉個(gè)簡單的例子:類A繼承于類B,而類B又繼承于類C,則類A間接繼承于類C。這類似于某繼承人繼承了其父親的財(cái)產(chǎn),而其父親又繼承了其爺爺?shù)呢?cái)產(chǎn),那么該繼承人就是間接繼承了其爺爺?shù)呢?cái)產(chǎn)。當(dāng)然,C++中的繼承和現(xiàn)實(shí)生活中的情況一樣,其傳遞性是單向的,正如其爺爺不可能再繼承該繼承人的財(cái)產(chǎn)。根據(jù)繼承關(guān)系中基類的數(shù)量,一般可以將繼承分為單一繼承和多重繼承兩種情況,這是因?yàn)橐粋€(gè)子類可以同時(shí)繼承于多個(gè)基類。所以,簡單明了地說,當(dāng)子類只有一個(gè)基類時(shí),稱之為單一繼承,而當(dāng)子類同時(shí)繼承于多個(gè)基類時(shí),稱之為多重繼承。圖9.1中的繼承關(guān)系均為單一繼承,而且可以很容易地看出這些繼承關(guān)系都是單向的,而且具有傳遞性。多重繼承其實(shí)是單一繼承的擴(kuò)展形式,因?yàn)樽宇惻c每個(gè)基類之間的繼承關(guān)系都可以看做是一個(gè)單獨(dú)的單一繼承。少用多重繼承不僅可以使程序的結(jié)構(gòu)變得簡單明了,而且可以增加程序代碼的可讀性。9.1.2組合的概念與語法相對于繼承來說,組合是一種更為簡單的代碼重用的方式。在一些C++?書籍中,將繼承稱作是一種“is-a”的關(guān)系,將組合稱作是一種“has-a”的關(guān)系。根據(jù)這個(gè)簡單的類比,可以很容易地理解組合與繼承在代碼重用方面的本質(zhì)區(qū)別:繼承是從已有的類中進(jìn)行派生或者新增所需要的屬性;而組合是在新類中創(chuàng)建已有類的對象,將其作為自己的類成員來使用。當(dāng)然,從代碼的重用性方面來說,組合和繼承都是在已有的類的基礎(chǔ)上來創(chuàng)建新類。其實(shí),可以從另一個(gè)角度更形象地去理解組合:組合既然意味著一個(gè)類的成員是另一個(gè)類的成員,那么一般所有的程序中都有組合的影子,只不過一般的程序中的成員不是已有的類的對象,而是基本的數(shù)據(jù)類型的對象。而在其他面向?qū)ο蠡木幊陶Z言中,將基本數(shù)據(jù)類型也封裝成類,那么說一般的程序中用到了組合則更加形象。
在前面介紹繼承的概念時(shí),舉了狗是一種哺乳動(dòng)物的例子來說明繼承的關(guān)系,此處現(xiàn)在還是通過這個(gè)例子來介紹組合的概念?,F(xiàn)在有類Head、類Body和類Tail,那么在創(chuàng)建Dog的實(shí)體類的時(shí)候,就可以在其內(nèi)部分別定義Head類、Body類和Tail類的對象來構(gòu)造Dog類,就像拼裝一樣,將已有的東西拼湊或者再增加一些新的屬性來創(chuàng)建一個(gè)全新的類。
classHead //定義Head類
{public: intsize; …};classBody //定義Body類
{public: doublewidth; …};classTail //定義Tail類
{public:
doublelength; …};classDog //構(gòu)造Dog類
{ Headhead; //創(chuàng)建Head類的對象
Bodybody; //創(chuàng)建Body類的對象
Tailtail; //創(chuàng)建Tail類的對象
};
在繼承中,無論繼承方式如何,子類都無法訪問基類的私有成員。在組合中,新類將已存在的類作為它的成員,即使是自己的成員,無論是public、private還是protected成員,同樣不能訪問。也就是說,不管是在繼承還是在組合中,都不能訪問已存在的類私有變量或方法。除非通過其內(nèi)部定義的public方法來獲取它的私有變量成員,get()方法就是解決方案之一。
前面已經(jīng)介紹過,從代碼的重用性方面來說,組合和繼承都是在已有的類的基礎(chǔ)上來創(chuàng)建新類,但是在使用的選擇方面則有所不同。繼承在使用已存在的類時(shí),可以根據(jù)自身的需求對其進(jìn)行修改和增加,以滿足特定的要求。組合在對已存在的類進(jìn)行使用時(shí),只能原封不動(dòng)地將其實(shí)例化,并且將實(shí)例化的對象拿過來使用,而不能做任何修改,所以這在一定程度上也顯示了組合在代碼重用方面的局限性。當(dāng)然,如果需要對一個(gè)類進(jìn)行較大的修改而不是擴(kuò)展的時(shí)候,繼承就不如構(gòu)造一個(gè)新的類了。
因此,在對代碼進(jìn)行重用時(shí),是選擇繼承還是選擇組合,需要視具體情況而定,看所需要的重用是否需要對已有代碼進(jìn)行修改或擴(kuò)充。在后面章節(jié)中將要介紹的多態(tài)的概念中常見的動(dòng)態(tài)綁定和向上轉(zhuǎn)型時(shí),多用到繼承。9.2繼承方式本節(jié)將討論繼承方式對繼承的影響。一般將繼承方式分為三種:公有繼承(public)、私有繼承(private)和保護(hù)繼承(protected)。繼承方式的作用是控制子類對基類成員的訪問權(quán)限,這類似于普通的類的成員的存取控制,從它們相同的關(guān)鍵字就能看出它們的相似性了。類的protected成員和private成員都是對外不可訪問的。在繼承中,基類的protected成員對子類是可見的,也就是說,子類可以繼承基類的protected成員。因此,在沒有繼承層次結(jié)構(gòu)關(guān)系時(shí),可以認(rèn)為private修飾符和protected修飾符沒有什么區(qū)別。9.2.1私有繼承繼承方式的關(guān)鍵字在缺省時(shí)默認(rèn)表示為private,即私有繼承。所以在需要聲明私有繼承時(shí),可以顯式地聲明,也可以缺省。私有繼承可以將那些從基類中繼承而來的成員作為自己的私有成員來使用,而且只能當(dāng)作私有的。換句話來說,基類中可以被繼承的成員(包括public成員和protected成員)在子類中都變成了子類的私有成員。根據(jù)私有繼承的定義,它的使用只是代表著對繼承的實(shí)現(xiàn),而基類中的接口或者其他成員在對子類的使用中都會(huì)被隱藏掉,也就是說,在子類中僅僅是看了基類的對象卻無法使用。如果只是為了將基類的可繼承的成員繼承過來當(dāng)作private成員,還不如直接重新定義新的private成員來得更快,效率更高。但如果需要在外部使用這種成員就需要在子類的構(gòu)造中為其添加上public聲明即可。如下例所示:
【程序9.2】#include<iostream>#include<string>usingnamespacestd;
classPerson{public: stringgetName(){return"Name";}stringgetSex(){return"Sex";} intgetAge(){return20;}};classStudent:privatePerson{public: usingPerson::getName; usingPerson::getSex;};main(){ Students; s.getName(); //可以調(diào)用,已將其聲明為public成員
s.getSex(); //可以調(diào)用,已將其聲明為public成員
s.getAge(); //不可以調(diào)用,因?yàn)槭莗rivate成員
return0;}
這個(gè)例子中,getAge函數(shù)在基類Person類中雖是public成員,但是在私有繼承后,在子類Student類中就變成private成員了;而getName函數(shù)和getSex函數(shù)本應(yīng)該與getAge函數(shù)一樣,在基類中為private成員,但是因?yàn)樵谧宇惖臉?gòu)造中將其聲明為public成員,所以此時(shí)在外界就可以調(diào)用子類中的這兩個(gè)函數(shù)了。從這里可以看出,當(dāng)基類中有某些(而不是全部)接口或成員不希望在子類中被外界所訪問時(shí),就需要私有繼承了,而那些允許被外界訪問的接口或成員,就可以在子類中重新為其聲明為public成員。所以私有繼承的作用是用來隱藏基類中某些不想被外界使用的功能。此處需要注意的是,在子類中雖然可以還原成員在基類中的訪問權(quán)限,但是也僅僅是還原,并不能使其訪問權(quán)限與在基類中不一致。例如,在基類中為protected成員,在私有繼承的子類中只能將其還原為protected成員,而不能是public成員。在私有繼承時(shí),基類成員對于子類來說,public成員和protected成員是可見的;而對于子類的對象來說,基類成員的可見性與一般類及其對象的可見性相同,即public成員是可見的,而其他成員是不可見的。而且在私有繼承時(shí),基類的所有可繼承的成員在被子類繼承后都變?yōu)樽宇惖乃接谐蓡T,所以基類的成員只能由直接派生類訪問,而不能再往下繼承了,即無法實(shí)現(xiàn)多層次的私有繼承。9.2.2受保護(hù)繼承前面已介紹過,一個(gè)類的protected成員不能被類外部的其他用戶所訪問,但是可以被該類的子類所訪問。也就是說,對于外界來說,基類中的某些接口或成員想對外界隱藏起來,但是不對它的子類隱藏,允許其訪問。受保護(hù)繼承在很大程度上和私有繼承類似,在受保護(hù)繼承中,基類的public成員和protected成員在子類中都成為其protected成員,而私有成員仍不能被繼承。和私有繼承一樣,受保護(hù)繼承也有個(gè)較難區(qū)分的概念:在受保護(hù)繼承時(shí),基類成員對于子類來說,public成員和protected成員都是可見的;而對于子類的對象來說,基類成員的可見性與一般類及其對象的可見性相同,即public成員是可見的,而其他成員是不可見的。因此,我們也可以順理成章地推出,在受保護(hù)繼承時(shí),基類的成員也只能由直接派生類訪問,而無法再往下繼承。9.2.3公有繼承公有繼承讓基類中能夠被繼承的成員(public成員和protected成員)的訪問控制權(quán)限在公有繼承的子類中保持不變,即基類的public成員在子類中依然保持public類型,而基類的protected成員在子類中依然保持protected類型。公有繼承是用得最廣泛的繼承方式?;悓ο蟮目梢娦詫τ谧宇惖膶ο髞碚f,與一般類及其對象的可見性相同,即public成員是可見的,而其他成員是不可見的。在這里,protected成員與private成員是一樣的,沒有什么區(qū)別。9.2.4多重繼承在前面介紹繼承的語法時(shí),已經(jīng)提到過,根據(jù)基類的數(shù)量可以將繼承分類,當(dāng)基類的數(shù)量大于一個(gè)時(shí),稱這樣的繼承為多重繼承。下面來看看多重繼承的基本語法:
class子類:繼承方式基類A,繼承方式基類B…{ //類成員
};
在這里,每一個(gè)基類都是相對獨(dú)立的,因?yàn)閺哪撤N程度上來說,多重繼承其實(shí)就是單一繼承的一種擴(kuò)展形式,因?yàn)樽宇惻c每個(gè)基類之間的繼承關(guān)系都可以看做是一個(gè)單獨(dú)的單一繼承。
多重繼承時(shí),各個(gè)基類的繼承方式與單一繼承其實(shí)是一樣的,可以是公有繼承、私有繼承或受保護(hù)繼承,而且每個(gè)基類的繼承方式都可以不一樣,各個(gè)基類之間只需要用逗號(hào)隔開即可。
【程序9.3】#include<iostream>usingnamespacestd;classA{protected: inta;public:voiddisplayA(){ cout<<A:":a="<<a<<endl;}};classB{protected: intb;public: voiddisplayB() { cout<<"B::b="<<b<<endl;}};//類C同時(shí)繼承了類A和類BclassC:publicA,publicB{public: voidset(inti,intj) { a=i; b=j;}};intmain(){ Cc; c.set(5,6); c.displayA(); //調(diào)用類A的函數(shù)
c.displayB(); //調(diào)用類B的函數(shù)
return0;}
運(yùn)行結(jié)果如下:
在這個(gè)例子中可以很清楚地看到,類C同時(shí)繼承了類A的屬性a和類B的屬性b。但是在多重繼承中,很容易出現(xiàn)的錯(cuò)誤就是在不同的基類中出現(xiàn)同名,從而會(huì)發(fā)生命名的沖突問題。比如,在不同的基類中出現(xiàn)了同名的屬性或方法名,那么子類該繼承哪一個(gè)呢?如在上例中,如果同時(shí)將類A中的displayA()函數(shù)和類B中的displayB()函數(shù)都改名為display(),那么該程序便無法通過編譯。因?yàn)楫?dāng)類C同時(shí)繼承類A和類B時(shí),無法告訴編譯器它將要繼承哪個(gè)基類的display()函數(shù),編譯器會(huì)提示錯(cuò)誤:display()isambiguous(不明確的)。當(dāng)然,這個(gè)問題是可以解決的,即在調(diào)用同名的成員時(shí),在前面加上作用域就可以將同名的成員區(qū)分開來。比如在上例中,兩個(gè)打印結(jié)果的函數(shù)都改名為display()后,在調(diào)用時(shí)可以這樣:
c.A::display(); //調(diào)用類A的display函數(shù)
c.B::display(); //調(diào)用類B的display函數(shù)
這樣,編譯器就可以很清楚地知道程序員到底想要調(diào)用哪個(gè)基類中的同名成員了,從而消除了同名的沖突問題。這種方法只是解決多重繼承所帶來的多義性問題中的解決方案之一,另外還有一種比較高效的方法,就是在下一章將要介紹的虛繼承,在此就不詳細(xì)闡述了。一般來說,建議少用多重繼承,因?yàn)槎嘀乩^承相對于單一繼承來說存在較多的隱患。除了上面提到的同名產(chǎn)生的二義性(多義性)問題外,在較大規(guī)模項(xiàng)目的開發(fā)過程中,繼承不僅數(shù)量多,而且其關(guān)系層次很復(fù)雜,此時(shí)不僅較難消除同名帶來的二義性問題,還會(huì)產(chǎn)生很多新的問題。實(shí)際上,對于初學(xué)者或者在一般的小型開發(fā)中,單一繼承所能實(shí)現(xiàn)的功能就已經(jīng)夠用了。
下面再介紹一下容易與多重繼承混淆的多級繼承。在前面已經(jīng)介紹過,繼承是具有單向傳遞性的,一個(gè)子類繼承于一個(gè)基類的同時(shí)也可以是另外一個(gè)類的基類,即它也可以有自己的子類,此時(shí)就可以稱這三個(gè)類之間的關(guān)系結(jié)構(gòu)為多級繼承。這三個(gè)類之間的關(guān)系也就類似于爺爺、父親和孫子的三代關(guān)系。在多級繼承的關(guān)系結(jié)構(gòu)中,基類中的成員的訪問控制權(quán)限在子類中是依次傳遞的,但需要注意的是,這需要有個(gè)大前提,即在多級繼承中,每一層的繼承關(guān)系均為public繼承。因?yàn)橹挥衟ublic繼承能對原有成員的訪問控制權(quán)限保持連貫性;在前面也介紹過,當(dāng)private繼承和protected繼承時(shí),基類的成員都只能由它的直接子類訪問,而無法再繼續(xù)往下繼承。9.3派生類的構(gòu)造與析構(gòu)在C++的繼承結(jié)構(gòu)中,對于子類的構(gòu)造和析構(gòu)方面最重要的問題是:子類和基類的構(gòu)造函數(shù)分別在什么時(shí)候調(diào)用、誰先調(diào)用;在多重繼承時(shí),各個(gè)基類的構(gòu)造函數(shù)的調(diào)用順序和析構(gòu)函數(shù)的調(diào)用順序。9.3.1成員對象的初始化在創(chuàng)建一個(gè)類的對象時(shí),都會(huì)調(diào)用它的構(gòu)造函數(shù),那么在繼承關(guān)系中,當(dāng)需要?jiǎng)?chuàng)建一個(gè)子類的對象時(shí),如何調(diào)用它的構(gòu)造函數(shù)呢?它和基類的構(gòu)造函數(shù)又有什么關(guān)系呢?下面舉例說明?!境绦?.4】#include<iostream>usingnamespacestd; classA{public: A(){cout<<"ConstructorA"<<endl;} ~A(){cout<<"DeconstructedA"<<endl;}};classB:publicA{public: B(){cout<<"ConstructorB"<<endl;} ~B(){cout<<"DeconstructedB"<<endl;}};
main(){ Bb;}
運(yùn)行結(jié)果如下:
從這個(gè)程序片段的例子可以看出,在創(chuàng)建子類的對象時(shí),不僅僅要調(diào)用自身的構(gòu)造函數(shù),而且和基類的構(gòu)造函數(shù)也有關(guān)系。通過上面的例子還可以看出,在創(chuàng)建子類的對象時(shí),是先調(diào)用基類的構(gòu)造函數(shù),然后才調(diào)用自身的構(gòu)造函數(shù),這種順序?qū)τ诶^承的理解也是很重要的。
在進(jìn)行析構(gòu)時(shí),調(diào)用子類和基類的析構(gòu)函數(shù)的順序則和構(gòu)造函數(shù)的調(diào)用順序恰恰相反,這點(diǎn)也不難理解。其實(shí),在這里列舉的都是比較簡單的例子,因?yàn)樵谧宇惖臉?gòu)造函數(shù)和基類的構(gòu)造函數(shù)中都沒有參數(shù)。如果它們的構(gòu)造函數(shù)中需要參數(shù)又該怎么辦呢?因?yàn)椴豢赡芟葮?gòu)造出基類的對象再給它的各個(gè)成員賦值,這樣顯然是不正確的。這個(gè)時(shí)候需要將參數(shù)傳給適當(dāng)?shù)幕惖臉?gòu)造函數(shù),來達(dá)到這個(gè)目的。
【程序9.5】#include<iostream>usingnamespacestd;classA{protected: inta;public: A(inti) { a=i; cout<<"ConstructorA"<<endl;}~A(){cout<<"DeconstructedA"<<endl;}};classB:publicA{ intb;public: B(inti,intj):A(j) { b=i; cout<<"ConstructorB"<<endl;}~B(){cout<<"DeconstructedB"<<endl;}voidprint(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl;}};intmain(){ BobjB(10,20); objB.print();return0;}
運(yùn)行結(jié)果如下:
在上面的程序片段中,子類B的構(gòu)造函數(shù)有i和j兩個(gè)int類型的值,但是子類B自身的構(gòu)造函數(shù)只是使用了參數(shù)i而已,同時(shí)將另一個(gè)參數(shù)j傳給基類A的相應(yīng)的構(gòu)造函數(shù):
B(inti,intj):A(j)
一般來說,子類的構(gòu)造函數(shù)在聲明它自身所需要的參數(shù)的同時(shí),也必須聲明基類所需要的參數(shù),并且將參數(shù)傳遞給基類相對應(yīng)的構(gòu)造函數(shù)。因?yàn)樵诶^承中,基類的構(gòu)造函數(shù)的參數(shù)必須由子類的構(gòu)造函數(shù)傳遞給它。即使子類自身的構(gòu)造函數(shù)不需要任何參數(shù),但如果基類的構(gòu)造函數(shù)需要參數(shù),那么子類的構(gòu)造函數(shù)在被調(diào)用的時(shí)候也需要基類的構(gòu)造函數(shù)相應(yīng)的參數(shù),用來傳遞給基類。在組合中,是將其他的類作為自身的成員來使用,那么它們之間的構(gòu)造函數(shù)關(guān)系是怎么樣的呢?其實(shí)這和繼承很類似:在組合的結(jié)構(gòu)中,如果要?jiǎng)?chuàng)建新類的對象,則必須先調(diào)用它的成員類的構(gòu)造函數(shù),然后才調(diào)用自身的構(gòu)造函數(shù)。9.3.2構(gòu)造次序?qū)τ谄胀ńY(jié)構(gòu)層次的程序來說,不同類的構(gòu)造函數(shù)的調(diào)用順序和創(chuàng)建它們各自對象的順序是一致的。但是,對于多重繼承來說,卻并不是這樣的。在介紹多重繼承的語法時(shí)可以看到,聲明子類時(shí)在子類的名稱后面均是它所繼承的基類,而且排列是有一定順序的,可以暫且稱之為類繼承表順序。對于類的成員來說,這些基類的排列順序并沒有多大的意義,但是對于討論多重繼承中的構(gòu)造函數(shù)的調(diào)用順序卻是至關(guān)重要的。在多重繼承中子類和基類的構(gòu)造函數(shù)的調(diào)用順序與單一繼承一樣,要?jiǎng)?chuàng)建子類的對象必須先調(diào)用基類的構(gòu)造函數(shù)再調(diào)用子類自身的構(gòu)造函數(shù)。那么在多重繼承中,多個(gè)基類的構(gòu)造函數(shù)之間的調(diào)用順序是怎樣的呢?下面舉例說明?!境绦?.6】#include<iostream>usingnamespacestd;classA{public: A(){cout<<"ConstructorA"<<endl;}~A(){cout<<"DeconstructedA"<<endl;}};classB{public: B(){cout<<"ConstructorB"<<endl;}~B(){cout<<"DeconstructedB"<<endl;}};classC:publicA,publicB{public: C(){cout<<"ConstructorC"<<endl;}~C(){cout<<"DeconstructedC"<<endl;}};intmain(){ CobjC; return0;}
運(yùn)行結(jié)果如下:
從上面的程序可以看出,當(dāng)子類有多個(gè)基類時(shí),這些基類的構(gòu)造函數(shù)的調(diào)用順序和它們在類繼承表中出現(xiàn)的順序一致,不論它們的成員在子類的成員初始化列表中出現(xiàn)的順序如何。
關(guān)于虛繼承需要說明的一點(diǎn)是:在多繼承中,如果有虛基類,那么不管在類繼承表中聲明的順序如何,虛基類的構(gòu)造函數(shù)在任何非虛基類的構(gòu)造函數(shù)之前調(diào)用。此外,如果在類的繼承關(guān)系結(jié)構(gòu)中有多個(gè)虛基類的實(shí)例,那么虛基類只被初始化一次,即它的構(gòu)造函數(shù)只被調(diào)用一次。在多重繼承中,當(dāng)基類的構(gòu)造函數(shù)需要參數(shù)時(shí),和單一繼承類似,也是由子類的構(gòu)造函數(shù)將參數(shù)傳遞給它們。
【程序9.7】#include<iostream>usingnamespacestd;classA{protected: inta;public: A(inti) { a=i; cout<<"ConstructorA"<<endl;} ~A(){cout<<"DeconstructedA"<<endl;}};classB{protected: intb;public: B(inti) { b=i; cout<<"ConstructorB"<<endl; } ~B(){cout<<"DeconstructedB"<<endl;}};classC:publicA,publicB{ intc;public: C(inti,intj,intk):A(j),B(k) { c=i; cout<<"ConstructorC"<<endl; } voidprint() { cout<<"a="<<a<<endl;cout<<"b="<<b<<endl; cout<<"c="<<c<<endl; } ~C(){cout<<"DeconstructedC"<<endl;}};intmain(){ CobjC(10,20,30); objC.print(); return0;}
運(yùn)行結(jié)果如下:從這個(gè)例子可以看出,在多重繼承時(shí),構(gòu)造函數(shù)的參數(shù)的傳遞其實(shí)和單一繼承是類似的,只需注意對于不同的基類的構(gòu)造函數(shù),要傳遞相對應(yīng)的參數(shù)。下面再來看看多個(gè)成員類的組合。當(dāng)一個(gè)類中有不止一個(gè)的成員類時(shí),這些成員類的構(gòu)造函數(shù)的調(diào)用順序和它們在新類中的聲明順序一樣,不論它們的成員在新類的成員初始化列表中出現(xiàn)的順序如何。如果同時(shí)存在繼承和組合,那么子類、基類以及子類里的成員類的構(gòu)造函數(shù)的調(diào)用順序又是怎樣的呢?下面詳細(xì)總結(jié)一下在繼承和組合中,構(gòu)造函數(shù)的調(diào)用的相關(guān)內(nèi)容:
(1)如果類里面有成員類,則成員類的構(gòu)造函數(shù)優(yōu)先被調(diào)用;(組合)
(2)如果要?jiǎng)?chuàng)建子類的對象,則基類的構(gòu)造函數(shù)優(yōu)先被調(diào)用,同時(shí)也優(yōu)先于派生類里的成員類;(繼承和組合同時(shí)存在)
(3)如果有多個(gè)基類,則構(gòu)造函數(shù)的調(diào)用順序是各個(gè)基類在類繼承表中出現(xiàn)的順序而不是它們在成員初始化表中的順序;(多重繼承)
(4)成員類對象構(gòu)造函數(shù):如果有多個(gè)成員類對象,則構(gòu)造函數(shù)的調(diào)用順序是對象在類中被聲明的順序而不是它們出現(xiàn)在成員初始化表中的順序;(多個(gè)成員類的組合)
(5)子類構(gòu)造函數(shù):作為一般規(guī)則,子類構(gòu)造函數(shù)不能直接向一個(gè)基類數(shù)據(jù)成員賦值而是把值傳遞給適當(dāng)?shù)幕悩?gòu)造函數(shù);(構(gòu)造函數(shù)的參數(shù)的傳遞)
(6)虛基類的構(gòu)造函數(shù)在任何非虛基類構(gòu)造函數(shù)前被調(diào)用;(虛基類的調(diào)用順序)。
(7)如果類繼承中包括多個(gè)虛基類的實(shí)例,則基類只被初始化一次。
【程序9.8】
classbase;
classbase2;
classlevel1:publicbase2,virtualpublicbase;
classlevel2:publicbase2,virtualpublicbase;
classtoplevel:publiclevel1,virtualpubliclevel2;
toplevelview;其構(gòu)造函數(shù)的調(diào)用順序如下:
base(); //虛基類僅被構(gòu)造一次,而且優(yōu)先于非虛基類的構(gòu)造函數(shù)被調(diào)用
base2();
level2(); //虛基類
base2();
level1();
toplevel();
9.3.3析構(gòu)次序不論構(gòu)造函數(shù)的調(diào)用順序如何,永遠(yuǎn)都遵循一個(gè)定理:先構(gòu)造后析構(gòu),后構(gòu)造先析構(gòu)。例如,在繼承中,子類的構(gòu)造函數(shù)在基類的構(gòu)造函數(shù)之后調(diào)用,那么子類的析構(gòu)函數(shù)就要先調(diào)用,然后才調(diào)用基類的析構(gòu)函數(shù)。9.4派生類的使用9.4.1類對象創(chuàng)建與使用下面系統(tǒng)地介紹一下關(guān)于子類對象的創(chuàng)建與使用的方法。創(chuàng)建子類的對象其實(shí)和普通類的對象的創(chuàng)建一樣,只是在調(diào)用子類的構(gòu)造函數(shù)之前,會(huì)先調(diào)用基類的構(gòu)造函數(shù),通過基類相應(yīng)的構(gòu)造函數(shù)為那些從基類繼承而來的成員進(jìn)行初始化。下面再來看看9.3.2節(jié)中第二個(gè)關(guān)于向基類構(gòu)造函數(shù)傳遞相應(yīng)參數(shù)為其成員進(jìn)行初始化的例子中的一個(gè)片段:classC:publicA,publicB{ intc;public: C(inti,intj,intk):A(j),B(k) { c=i; cout<<"ConstructorC"<<endl;}~C(){cout<<"DeconstructedC"<<endl;}voidprint(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl;cout<<"c="<<c<<endl;}
};intmain(){ CobjC(10,20,30); objC.printf();
return0;
}類C從類A和類B中分別繼承了成員變量a和b,并通過調(diào)用類A和類B相應(yīng)的構(gòu)造函數(shù)對這兩個(gè)成員變量進(jìn)行初始化,所以此時(shí)類C中的a為20,b為30,c為10。這種為基類的構(gòu)造函數(shù)傳遞參數(shù)的方式是唯一能夠初始化從基類繼承而來的成員變量的方式,因?yàn)椴豢赡芟葎?chuàng)建基類的實(shí)例對象,再為其成員變量賦值。因此,必須在調(diào)用子類構(gòu)造函數(shù)來進(jìn)行初始化的同時(shí),將基類構(gòu)造函數(shù)所需的參數(shù)傳遞給它,讓其進(jìn)行關(guān)于從基類繼承而來的成員變量的初始化工作。上面的例子中,在主函數(shù)中創(chuàng)建了子類C的對象objC,和普通的類成員變量一樣,類中的private成員和protected成員是不能被外界所訪問的;在類C中,從類A和類B中分別繼承來的a和b以及類C中的c均為類中默認(rèn)的private成員屬性。因此,即使類C的實(shí)例化對象objC的成員變量已經(jīng)被初始化,也無法被外界所訪問。但是有一個(gè)特殊的情況,如果這個(gè)子類還有自己的子類,即在作為其他類的子類的同時(shí)自己也作為一個(gè)基類,那么這時(shí)它的子類是可以訪問它的protected成員的。protected關(guān)鍵字與private關(guān)鍵字的不同就體現(xiàn)在繼承結(jié)構(gòu)關(guān)系中,類的protected成員是可以被子類所訪問的??偟膩碚f,子類的對象在創(chuàng)建和使用方面,大部分都與普通的類的對象的創(chuàng)建與使用方法一樣,只是需要注意子類中從基類繼承來的成員變量的訪問權(quán)限的特殊之處。9.4.2向上映射當(dāng)需要將子類的對象、引用或指針轉(zhuǎn)化成基類的對象、引用或指針時(shí),稱這種操作為“向上映射”。
【程序9.9】
#include<iostream>
#include<string>
usingnamespacestd;
classA
{
public: voidprint()
{ cout<<"基類成員函數(shù)"<<endl;
}
};
classB:publicA
{
public: voidprint() { cout<<"子類成員函數(shù)"<<endl;
}
};
voidprint(Aa) //需要調(diào)用基類的引用
{ a.print(); //調(diào)用基類的成員函數(shù)
}
intmain()
{ Bb; //聲明子類的對象
print(b); //實(shí)現(xiàn)“向上映射”
return0;
}運(yùn)行結(jié)果如下:本來外部的print()函數(shù)希望接收的參數(shù)是基類的對象,但是卻將一個(gè)子類的對象作為參數(shù)對其進(jìn)行調(diào)用,這是一種合理的做法。因?yàn)樵谇懊娼榻B繼承的時(shí)候就已經(jīng)說過,繼承是一種“is-a”的結(jié)構(gòu)關(guān)系,所以,一般來說,將特殊的賦值給一般的,這種做法對于編譯器來說是安全的。但是,對于函數(shù)的調(diào)用者來說,傳入的是子類的對象,自然希望能夠調(diào)用子類的print函數(shù)。那么,雖然程序語法是正確的,表現(xiàn)出來的結(jié)果卻不是程序員想要的。這種現(xiàn)象的出現(xiàn)是由于早捆綁引起的。所謂早捆綁,就是在程序運(yùn)行之前的編譯、連接階段就將函數(shù)體與函數(shù)的調(diào)用進(jìn)行了關(guān)聯(lián)。在C語言中,對所有函數(shù)的調(diào)用都是一種早捆綁。上面例子中的調(diào)用也是一種早捆綁,在編譯階段,編譯器就已經(jīng)將類外部的print()函數(shù)與基類中的成員關(guān)聯(lián)起來,所以在運(yùn)行時(shí),即使傳入的是子類的對象,執(zhí)行的依然是基類的成員函數(shù)。為了解決這種問題,通常的解決辦法是使用晚捆綁,即在程序的運(yùn)行階段將函數(shù)的調(diào)用與函數(shù)體關(guān)聯(lián)起來,以對象為依據(jù),確定具體要調(diào)用哪個(gè)函數(shù)。也就是說,晚捆綁相對于早捆綁來說,體現(xiàn)的是一種面向?qū)ο蟮乃枷?。但是,晚捆綁的使用必須與虛函數(shù)(用virtual關(guān)鍵字修飾)結(jié)合在一起。關(guān)于虛函數(shù)的概念將在下一章關(guān)于多態(tài)的內(nèi)容中再介紹,這里就不詳細(xì)說明了。向上映射是安全的,但是向下映射就不安全了,因?yàn)闊o法將一般化的東西賦值給特殊化的。比如,定義了一個(gè)類Bird(鳥),其中定義了一個(gè)private的成員函數(shù)fly()。再定義一個(gè)類Ostrich(鴕鳥),它繼承于Bird類。此時(shí),不可能將Bird類的對象賦給Ostrich類,因?yàn)轼r鳥是不會(huì)飛的,無法為其調(diào)用fly()函數(shù)。向下映射不僅不具有安全性,而且向下映射的實(shí)現(xiàn)必須通過類型的強(qiáng)制轉(zhuǎn)換才能實(shí)現(xiàn)。所以建議盡量不要使用向下映射。9.4.3指針和引用的向上類型轉(zhuǎn)換子類在從基類繼承成員的時(shí)候,可以加上一些自己特有的成員屬性或函數(shù)。那么,在進(jìn)行向上映射的時(shí)候,子類中這些“超出”基類的成員部分將會(huì)被編譯器如何處理呢?將子類的對象轉(zhuǎn)化成基類的對象時(shí),向上映射會(huì)將子類中那些“超出”基
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(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ǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 校長在迎國慶歌唱比賽上的總結(jié)發(fā)言
- 小學(xué)2025年度教學(xué)工作計(jì)劃
- 《小小營養(yǎng)師》課件大班健康活動(dòng)
- 路基施工質(zhì)量控制措施
- 二零二五年度講師兼職與全職工作合同3篇
- 2024年深圳信息職業(yè)技術(shù)學(xué)院高職單招語文歷年參考題庫含答案解析
- 二零二五年度新型城鎮(zhèn)化建設(shè)項(xiàng)目裝飾勞務(wù)分包合同模板3篇
- 二零二五年度金融借貸履約擔(dān)保合同3篇
- 三節(jié)光譜法儀器與光學(xué)器件培訓(xùn)講學(xué)
- 2024年濟(jì)南工程職業(yè)技術(shù)學(xué)院高職單招職業(yè)技能測驗(yàn)歷年參考題庫(頻考版)含答案解析
- 2025版健康體檢中心代理運(yùn)營合同協(xié)議3篇
- (已壓縮)礦產(chǎn)資源儲(chǔ)量技術(shù)標(biāo)準(zhǔn)解讀300問-1-90
- 《戶用光伏發(fā)電系統(tǒng)技術(shù)導(dǎo)則》
- 寒假作業(yè)(試題)2024-2025學(xué)年五年級上冊數(shù)學(xué) 人教版(十二)
- (2024)江西省公務(wù)員考試《行測》真題卷及答案解析
- 采購部門總結(jié)及規(guī)劃
- 期末綜合試卷(含答案)2024-2025學(xué)年蘇教版數(shù)學(xué)四年級上冊
- 銀行信息安全保密培訓(xùn)
- 市政道路工程交通疏解施工方案
- 2024年部編版初中七年級上冊歷史:部分練習(xí)題含答案
- 《中華人民共和國藥品管理法實(shí)施條例》
評論
0/150
提交評論