繼承
成都創(chuàng)新互聯(lián)公司專注于鐵嶺縣企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,商城建設(shè)。鐵嶺縣網(wǎng)站建設(shè)公司,為鐵嶺縣等地區(qū)提供建站服務(wù)。全流程按需求定制開發(fā),專業(yè)設(shè)計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)1、私有繼承:基類的公有成員和保護成員都作為派生類的私有成員,并且不能被這個派生類的子類所訪問。
公有繼承:基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的訪問權(quán)限,而基類的私有成員在派生類中是不可見的。
在公有繼承時,派生類的成員函數(shù)可以訪問基類中的公有成員和保護成員;派生類的對象僅可以訪問基類中的公有成員。
保護繼承:基類的所有公有成員和保護成員都成為派生類的保護成員,并且只能被它的派生類成員函數(shù)訪問,不能被它派生類的對象訪問。
2、注意:
1》基類的private成員在派生類中是不能被訪問的,如果基類成員不想在類外被直接訪問,但需要在派生類中訪問,就需要定義成protected。可以看出保護成員限定符是因繼承才出現(xiàn)的。
2》public繼承是一個接口繼承,保持is-a原則,每個父類可用的成員對子類也可以用,因為每個子類對象也都是一個父類對象。
3》procted/private繼承是一個實現(xiàn)繼承,基類的部分成員并非完全成為子類接口的一部分,是has-a的關(guān)系原則,所以非特殊情況下不會使用這兩種繼承關(guān)系,在絕大部分的場景下使用的都是公有繼承。
4》不管是哪種繼承方式,在派生類內(nèi)部都可以訪問基類的公有成員和保護成員,基類的私有成員存在但是在子類中不可見(不可以訪問)
5》使用class關(guān)鍵字是默認(rèn)的繼承方式是private,使用struct時默認(rèn)的繼承方式是public,不過最好顯示的寫出繼承方式。
6》在實際應(yīng)用中一般使用的都是public繼承,極少場景下才會使用protected/private繼承。
3、派生類的默認(rèn)成員函數(shù):
在繼承關(guān)系里面,在派生類中如果沒有顯示的定義這六個成員函數(shù),編譯系統(tǒng)則會默認(rèn)合成這六個默認(rèn)的成員函數(shù)。
類的六個默認(rèn)成員函數(shù):
1》構(gòu)造函數(shù)
2》拷貝構(gòu)造函數(shù)
3》析構(gòu)函數(shù)
4》賦值操作重載
5》取地址操作符重載
6》const修飾的取地址操作符重載
4、繼承體系中的作用域:
1》在繼承體系中基類和派生類是兩個不同的作用域
2》子類和父類中有同名成員,子類成員將屏蔽父類對成員的直接訪問(在子類成員函數(shù)中,可以使用 基類::基類成員 訪問)
——隱藏:子類可以隱藏繼承的成員變量。對于子類可以從父類繼承成員變量,只要子類中定義的成員變量和父類中的成員變量相同時,子類就隱藏了繼承的成員變量,即子類對象以及子類自己聲明定義的方法操作的變量是子類重新定義的成員變量。 子類也可以隱藏已經(jīng)繼承的方法,子類通過重寫來隱藏繼承的方法。
方法重寫是指:子類中定義一個方法,并且這個方法的名字、返回值類型、參數(shù)個數(shù)與父類繼承的方法完全相同。子類通過方法的重寫可以把父類的狀態(tài)和行為改變?yōu)樽陨淼臓顟B(tài)和行為。
如果子類想使用被隱藏的父類方法,必須使用super關(guān)鍵字。
——重定義
3》注意在實際繼承體系里面最好不要定義同名成員。
5、派生類的構(gòu)造函數(shù):
1》派生類的數(shù)據(jù)成員包含了基類中說明的數(shù)據(jù)成員和派生類中說明的數(shù)據(jù)成員。
2》構(gòu)造函數(shù)不能被繼承,因此,派生類的構(gòu)造函數(shù)必須通過調(diào)用基類的構(gòu)造函數(shù)來初始化基類的數(shù)據(jù)成員。
3》如果派生類中還有子對象時,還應(yīng)包含對子對象初始化的構(gòu)造函數(shù)。
6、派生類構(gòu)造函數(shù)的調(diào)用順序:
1》基類的構(gòu)造函數(shù)(按照繼承列表中的順序調(diào)用)
2》派生類中對象的構(gòu)造函數(shù)(按照在派生類中成員對象聲明順序調(diào)用)
3》派生類構(gòu)造函數(shù)
注意:
1》基類沒有缺省構(gòu)造函數(shù),派生類必須要在初始化列表中顯示給出基類名和參數(shù)列表。
2》基類沒有定義構(gòu)造函數(shù),則派生類也可以不用定義,全部使用缺省構(gòu)造函數(shù)。
3》基類定義了帶有形參列表的構(gòu)造函數(shù),派生類就一定要定義構(gòu)造函數(shù)。
補充:缺省構(gòu)造函數(shù)又叫默認(rèn)構(gòu)造函數(shù)(default constructor)。當(dāng)聲明對象時,編譯器會調(diào)用一個構(gòu)造函數(shù)。若聲明的類中沒有聲明構(gòu)造函數(shù),編譯器就會自動調(diào)用一個缺省構(gòu)造函數(shù),該函數(shù)相當(dāng)于一個不接受任何參數(shù),不進行任何操作的構(gòu)造函數(shù)。而當(dāng)類中已經(jīng)有聲明的構(gòu)造函數(shù)時,編譯器就不會調(diào)用缺省構(gòu)造函數(shù)。
7、派生類的析構(gòu)函數(shù):由于析構(gòu)函數(shù)也不能被繼承,因此在執(zhí)行派生類的析構(gòu)函數(shù)時,基類的析構(gòu)函數(shù)也將被調(diào)用。
執(zhí)行順序是:
1》先執(zhí)行派生類的析構(gòu)函數(shù),
2》派生類包含成員對象的析構(gòu)函數(shù)(調(diào)用順序和成員對象在類中的聲明順序相反)
3》基類析析構(gòu)函數(shù)(調(diào)用順序和基類在派生列表中聲明的順序相反)
8、構(gòu)造函數(shù)的功能是在創(chuàng)建對象時,用給定的值對對象進行初始化。
沒有參數(shù)的構(gòu)造函數(shù)稱為默認(rèn)構(gòu)造函數(shù)(缺省構(gòu)造函數(shù))。默認(rèn)構(gòu)造函數(shù)有兩種:一種是系統(tǒng)自動提供的;另一種是程序員定義的。
使用系統(tǒng)提供的默認(rèn)構(gòu)造函數(shù)給創(chuàng)建的對象初始化時,外部類對象和靜態(tài)類對象的所有數(shù)據(jù)成員為默認(rèn)值,自動對象的所有數(shù)據(jù)成員為無意義值。
9、構(gòu)造函數(shù)分兩類:一類是帶參數(shù)的構(gòu)造函數(shù),可以是一個參數(shù),也可以是多個參數(shù);另一類是默認(rèn)構(gòu)造函數(shù),即不帶參數(shù)的構(gòu)造函數(shù)。
11、子類型:用來描述類型之間的一般和特殊的關(guān)系。當(dāng)有一個已知類型S,它至少另一個類型T的行為,它還可以包含自己的行為,這時,則稱類型S是類型T的子類型。子類型的概念涉及行為共享,即代碼重用問題,它與繼承有著密切的關(guān)系。在繼承中,公有繼承可以實現(xiàn)子類型。
子類型的重要性就在于減輕編寫代碼的負(fù)擔(dān),提高了代碼重用率。
因此一個函數(shù)可以用于某類型的對象,則它也可以用于該類型的各個子類型的對象,這樣就不用為處理這些子類型的對象去重載該函數(shù)。
12、類型適應(yīng):子類型與類型適應(yīng)是一致的,A類型是B類型的子類型,那么B類型必將適應(yīng)于A類型。
13、賦值兼容規(guī)則:通常在公有繼承方式下,派生類是基類的子類型,這時派生類對象與基類對象之間的一些關(guān)系規(guī)則稱為賦值兼容規(guī)則.
在公有繼承方式下,兼容規(guī)則規(guī)定:
1》子類對象可以賦值給父類對象
2》父類對象不能賦值給子類對象
3》父類的指針/引用可以指向子類對象
4》子類的指針/引用不能指向父類對象(可以通過強制類型轉(zhuǎn)換完成)
使用上述規(guī)則,必須注意兩點:
1》具有派生類公有繼承類的條件。
2》上述三條規(guī)定是不可逆的。
——賦值兼容規(guī)則:
1》為什么派生類可以給基類賦值?
——派生類訪問空間大于基類
3》父類指針/引用可以指向子類對象(多態(tài)實現(xiàn))
14、單繼承、多繼承、菱形繼承:
單繼承:一個子類只有一個直接父類時,稱這個繼承關(guān)系為單繼承
多繼承:一個子類有兩個或者兩個以上直接父類時,稱這個繼承關(guān)系為多繼承
菱形繼承:菱形繼承存在二義性和數(shù)據(jù)冗余的問題
——虛繼承解決了菱形繼承的二義性和數(shù)據(jù)冗余的問題。
1》虛繼承解決了在菱形繼承體系里面子類對象包含多分父類對象的數(shù)據(jù)冗余和浪費空間的問題。
2》虛繼承體系看起來很復(fù)雜,在實際應(yīng)用中我們通常不會定義如此復(fù)雜的繼承體系,一般不到萬不得已都不要定義菱形繼承的體系結(jié)構(gòu),英文使用虛繼承解決數(shù)據(jù)冗余問題也帶來了性能上的損耗。
15、什么情況下編譯器會合成一種缺省構(gòu)造函數(shù)?《深入理解C++對象模型》
——1》類中有類類型成員對象,該成員對象它有自己的缺省構(gòu)造函數(shù),這時編譯器也會在該類中合成一個缺省的構(gòu)造函數(shù)(不一定是繼承和派生的關(guān)系)
2》繼承層次,基類中有缺省構(gòu)造函數(shù),而派生類中沒有構(gòu)造函數(shù),這時編譯器就會在派生類中合成一個缺省的構(gòu)造函數(shù)
3》虛擬繼承時,在派生類中會合成缺省構(gòu)造函數(shù)
4》基類含有純虛函數(shù)時,在派生類中會合成缺省構(gòu)造函數(shù)。
——默認(rèn)構(gòu)造函數(shù)是編譯器默認(rèn)合成的
——缺省構(gòu)造函數(shù)是里面帶缺省值的
16、友元與繼承:
友元關(guān)系不能繼承,也就是說基類友元不能訪問子類私有和保護成員
注意:
友元可以是一個函數(shù),該函數(shù)被稱為友元函數(shù);友元也可以是一個類,該類被稱為友元類。
在C++中,自定義函數(shù)可以充當(dāng)友元,友元只是能訪問指定類的私有和保護成員的自定義函數(shù),不是指定類的成員,自然不能被繼承。
使用友元時要注意:
1》友元關(guān)系不能被繼承
2》友元關(guān)系是單向的,不具有交換性
3》友元關(guān)系不具有傳遞性
注意事項:
1》友元可以訪問類的私有成員
2》友元只能出現(xiàn)在類定義的內(nèi)部,友元聲明可以出現(xiàn)在任何地方,一般放在類定義的開始或者結(jié)尾。
3》友元可以是普通的非成員函數(shù),或者前面定義的其它類的成員函數(shù),或者整個類。
4》類必須將重載函數(shù)集中每一個希望設(shè)置為友元的函數(shù)都聲明為友元。
5》友元關(guān)系不能被繼承,基類的友元對派生類的成員沒有特殊的訪問權(quán)限。如果基類被授予友元關(guān)系,則只有幾類具有特殊的訪問權(quán)限。該基類的派生類不能訪問授予友元關(guān)系的類。
17、繼承與靜態(tài)成員:
基類定義了一個static成員,則整個繼承體系里面只有一個這樣的成員,無論派生多少個子類,都只有一個static成員實例
多態(tài)
1、對象的類型:
靜態(tài)多態(tài):編譯器在編譯期間完成的,編譯器根據(jù)函數(shù)實參的類型(可能會進行隱式類型轉(zhuǎn)換),可推斷出要調(diào)用哪一個函數(shù),如果有對應(yīng)的函數(shù)就調(diào)用該函數(shù),否則編譯錯誤。
動態(tài)多態(tài):(動態(tài)綁定)在程序執(zhí)行期間(非編譯期)判斷所引用對象的實際類型,根據(jù)其實際類型調(diào)用相應(yīng)的方法。
class Base { }; class Derive :public Base { }; int main() { int a; Base b; Derive d; Base *pb = &b;//pb 基類指針類型(靜態(tài)類型--編譯器在編譯過程中確定的類型);;動態(tài)類型(它所指向的類型)---Base* pb = &d; system( "pause"); return 0; } Base *pb=&b;
pb=&d;
——這里pb的類型發(fā)生了變化,也就是它的動態(tài)類型,它的動態(tài)類型為Derive *
2、多態(tài):函數(shù)重載也是一種多態(tài)。意思是具有多種形式或形態(tài)的情形
靜態(tài)類型的多態(tài):在編譯期間就確定的關(guān)系(早綁定)
動態(tài)類型的多態(tài):在執(zhí)行期間判斷所引用對象啊的實際類型,根據(jù)其實際類型決定調(diào)用的相應(yīng)方法(晚綁定)(通過虛函數(shù)的機制實現(xiàn))
——1》使用virtual實現(xiàn),
2》通過基類類型的引用或指針的調(diào)用。。在運行過程中找指針的指向,先定義一個基類的指針,在指向要調(diào)用的派生類
3》在基類中一定要加vietual,,派生類中可以不加;
4》在派生類中需要重新實現(xiàn)這個基類中方法。
3、
class Base { public: virtual void FunTest()//虛函數(shù) { cout << "Base::FunTest()" << endl; } }; class Derive :public Base//在派生類中包含F(xiàn)unTest(),除了基類中的構(gòu)造函數(shù)和析構(gòu)函數(shù)其余的均會被繼承 { public: virtual void FunTest()//虛函數(shù) { cout << "Derive::FunTest()" << endl; } }; int main() { Derive d; d.FunTest(); Base b; b.FunTest(); Base* pb = &b; pb->FunTest(); //這里調(diào)用的是b的FunTest pb = &d; pb->FunTest(); //這里調(diào)用的是d的FunTest system( "pause"); return 0; }
virtual:這個關(guān)鍵字可以實現(xiàn)多態(tài)。
需要注意:1》在基類中的虛函數(shù)前一定要加virtual關(guān)鍵字。在派生類中重寫該函數(shù)時,可加可不加virtual關(guān)鍵字。
2》調(diào)用時,要用基類的指針/引用指向派生類的對象
動態(tài)綁定條件:1》必須是虛函數(shù) 2》通過基類類型的引用或者指針調(diào)用
4、繼承體系中同名成員函數(shù)關(guān)系:
1》重載:
1》在同一個作用域;
2》函數(shù)名相同、參數(shù)不同
3》返回值可以不同
2》重寫(覆蓋):
1》不在同一作用域(分別在基類和派生類)
2》函數(shù)名相同、參數(shù)相同、返回值相同(協(xié)変例外)
3》基類函數(shù)必須有virtual關(guān)鍵字
4》訪問修飾符可以不同
3》重定義(隱藏):
1》在不同作用域中(分別在基類和派生類)
2》函數(shù)名相同
3》在基類和派生類中只要不構(gòu)成重寫就是重定義
5、構(gòu)造函數(shù)不能定義成virtual
---1》構(gòu)造函數(shù)是用來構(gòu)造對象,virtual需要通過對象調(diào)用,,而在構(gòu)造函數(shù)中的virtual,還沒有出構(gòu)造函數(shù),此時并沒有成功構(gòu)造對象,所以不行。
2》當(dāng)一個構(gòu)造函數(shù)被調(diào)用時,它要做的首要事情之一是初始化它的VPTR。因此,它只能知道它是當(dāng)前類的,而完全忽視這個對象后面是否還有繼承者。當(dāng)編譯器為這個構(gòu)造函數(shù)產(chǎn)生代碼時,它是為這個類的構(gòu)造函數(shù)產(chǎn)生代碼——既不是為基類,也不是為它的派生類(因為類不知道誰繼承他)。所以它使用的VPTR必須是對于這個類的VTABLE。而且,VPTR的狀態(tài)是由最后調(diào)用的構(gòu)造函數(shù)確定的。
但是,當(dāng)這一系列構(gòu)造函數(shù)正在發(fā)生時,每個構(gòu)造函數(shù)都已經(jīng)設(shè)置VPTR指向他自己的VTABLE。如果函數(shù)調(diào)用使用虛機制,它將只產(chǎn)生通過他自己的VTABLE的調(diào)用,而不是最后的VTABLE(所有構(gòu)造函數(shù)被調(diào)用之后才會有最后的VTABLE)
6、拷貝構(gòu)造不能定義成virtual,
7、賦值運算符重載可以定義成virtual一般情況下不建議這樣做。
8、靜態(tài)成員函數(shù)、友元函數(shù)不可以定義成虛函數(shù)----因為它們兩個沒有this指針,虛函數(shù)底層是用this指針實現(xiàn)的
——類的普通成員函數(shù)(包括虛函數(shù))在編譯時加入this指針,通過這種方式可以與對象捆綁,而靜態(tài)函數(shù)編譯時不加this指針,因為靜態(tài)函數(shù)是給所有的類對象公用的,因此在編譯時沒有加this指針,所以無法與對象捆綁,而虛函數(shù)是靠著與對象捆綁加上虛函數(shù)列表才實現(xiàn)了動態(tài)捆綁,所以沒有this指針虛函數(shù)無從談起。
——因為在一個類中聲明友元時,該友元不是自己的成員函數(shù),自然不能把它聲明為虛函數(shù)。
但是友元本身可以是虛函數(shù)。這個類將她聲明為自己的友元時,只是讓它可以存取自己的私有變量。
9、純虛函數(shù)
在成員函數(shù)的形參后面寫上=0,則成員函數(shù)為虛函數(shù),包含虛函數(shù)的類叫做抽象類(也叫接口類)抽象類不能實例化出對象,
純虛函數(shù)在派生類中重新定義以后,派生類才能實例化出對象。
class Test { virtual void FunTest() = 0;//沒有函數(shù)體,只是一個接口,必須在派生類中重新實現(xiàn) }; int main() { Test t;//不允許使用抽象類類型的對象,會報錯 system( "pause"); return 0; }
10、虛表
class Base { public: virtual void FunTest(){}//有一個虛指針 virtual void FunTest1(){} virtual void FunTest2(){} virtual void FunTest3(){} virtual void FunTest4(){} }; int main() { Base base; base.FunTest(); base.FunTest1(); base.FunTest2(); base.FunTest3(); base.FunTest4(); system( "pause"); return 0; }
class Drive :public Base { public: virtual void FunTest1() { ; } virtual void FunTest2() { ; } }; int main() { Drive d;//d里面包含一個base,而base里面同上,存的是一個虛指針,指向一個虛表,只不過在派生類中實現(xiàn)了的函數(shù),它的函數(shù)地址會發(fā)生改變 d.FunTest(); d.FunTest1(); d.FunTest2(); d.FunTest3(); d.FunTest4();//在派生類中實現(xiàn)了的虛函數(shù),就調(diào)用派生類中的,沒有在派生類中實現(xiàn)的就調(diào)用基類中的 system( "pause"); return 0; } int main() { Base base; Drive d; Base *pa = &base;//pa里面會有(指向)一個虛指針,指向虛表,再來調(diào)動虛函數(shù) pa->FunTest(); pa->FunTest1(); pa->FunTest2(); pa->FunTest3(); pa->FunTest4(); pa = (Base*)&d;//d中會有一個Base,Base里面會包含一個虛指針,指向另一個虛表;;;這里的類型轉(zhuǎn)換是不起作用的,并不會指向上一個虛表 pa->FunTest(); pa->FunTest1(); pa->FunTest2(); pa->FunTest3(); pa->FunTest4(); system( "pause"); return 0; }
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。