C++?繼承的范例講解
1.繼承的概念
繼承,是面向?qū)ο蟮娜筇匦灾弧@^承可以理解成是類級別的一個復(fù)用,它允許我們在原有類的基礎(chǔ)上進(jìn)行擴(kuò)展,增加新的功能。
當(dāng)創(chuàng)建一個類時,我們可以繼承一個已有類的成員和方法,并且在原有的基礎(chǔ)上進(jìn)行提升,這個被繼承的類叫做基類,而這個繼承后新建的類叫做派生類。
用法如下:
class [派生類名] : [繼承類型] [基類名]
例如:
class Person { public: string _name; int _age; }; class Student : public Person { protected: string _stuNum; };
這里的派生類Student就復(fù)用了Person的方法和成員,并在此基礎(chǔ)上擴(kuò)展補(bǔ)充。
2.繼承方式
繼承的方式和類的訪問限定符一樣,分為public(公有繼承),private(私有繼承), protected(保護(hù)繼承)三種。
不同的繼承方式,在派生類中繼承下來的基類成員的訪問權(quán)限也不一樣。
基類的其他成員在子類的訪問方式 = Min(成員在基類的訪問限定符,繼承方式)
備注:
1.在實際運用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承,因為protetced/private繼承下來的成員都只能 在派生類的類里面使用,實際中擴(kuò)展維護(hù)性不強(qiáng)。
2.使用關(guān)鍵字class時默認(rèn)的繼承方式是private,使用struct時默認(rèn)的繼承方式是public,不過最好顯示的寫出繼承方式。
3.基類與派生類的賦值轉(zhuǎn)換
派生類可以賦值給基類的對象、指針或者引用,這樣的賦值也叫做對象切割。
例如上面的Person類和Student類
這種賦值只能是派生類賦給基類(但需要割掉多出來的成員例如_ stuID),而基類對象不能賦給派生類。
基類的指針可以強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針, 如:
int main() { Person p1; Student s1; Person* hPtr1 = &s1;//指向派生類對象 Person* hPtr2 = &p1;//指向基類對象 Student* pPtr = (Student*)hPtr1;//沒問題 Student* pPtr = (Student*)hPtr2;//有時候沒有問題,但是會存在越界風(fēng)險 return 0; }
小結(jié):
1.派生類可以賦值給基類的對象、指針或者引用
2.基類對象不能賦值給派生類對象
3.基類的指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針。**??但是必須是基類的指針是指向派生類對象時才是安全的,否則會存在越界的風(fēng)險。**這里基類如果是多態(tài)類型,可以使用RTT的dynamic_cast來進(jìn)行識別后進(jìn)行安全轉(zhuǎn)換。
4.作用域與隱藏
??隱藏:隱藏,也叫做重定義,當(dāng)基類和派生類中出現(xiàn)重名的成員時,派生類就會將基類的同名成員給隱藏起來,然后使用自己的。(但是隱藏并不意味著就無法訪問,可以通過指明基類作用域來顯式訪問隱藏成員。)
class Person { public: void f(int age) { cout << "姓名" << _name << endl; cout << "年齡" << _age << endl; } protected: string _name; int _age; }; class Student : public Person { public: void f() { Person::f(32);//需顯式調(diào)用f函數(shù) cout << "學(xué)號" << _stuNum << endl; } private: string _stuNum; };
例如這里的f( )就構(gòu)成了隱藏
同時,這里還有個需要注意的問題,在基類與派生類中,同名的方法并不能構(gòu)成重載,因為處于不同的作用域中。而只要滿足方法名相同,就會構(gòu)成隱藏。
5.派生類的默認(rèn)成員函數(shù)
在每一個類中,都會有6個默認(rèn)的成員函數(shù),這些函數(shù)即使我們自己不去實現(xiàn),編譯器也會幫我們實現(xiàn)。
??這里有兩點需要注意(筆試題??迹?/p>
1.構(gòu)造函數(shù),拷貝構(gòu)造,operator=三種情況,都要調(diào)用父類對應(yīng)的構(gòu)造函數(shù)/拷貝構(gòu)造/operator=進(jìn)行對父類的成員變量的初始化,并且倘若父類沒有默認(rèn)的構(gòu)造函數(shù)的時候(比如父類寫了帶參的構(gòu)造函數(shù)),我們就要顯式調(diào)用(Person(參數(shù)…),Person::operator=(參數(shù)…))
??構(gòu)造函數(shù)顯示調(diào)用
派生類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員。如果基類沒有默認(rèn)的構(gòu)造函 數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
Student() :People() { cout << "Student()" << endl; }
??拷貝構(gòu)造顯示調(diào)用
建議拷貝構(gòu)造都用顯示調(diào)用,不然免不了出現(xiàn)子類拷貝構(gòu)造當(dāng)中調(diào)用了父類的構(gòu)造函數(shù)的情況(因為拷貝構(gòu)造也是構(gòu)造,在初始化列表處,對于子類而言,父類相當(dāng)于一個自定義類型對象,子類會調(diào)用父類的構(gòu)造函數(shù)對父類的資源進(jìn)行初始化。)
Student(const Student& s) :People(s) { cout << "Student(const Student& s)" << endl; }
??派生類的operator=必須要調(diào)用基類的operator=完成基類的復(fù)制。
Student& operator=(const Student& s) { cout << "Student& operator = (const Student& s)" << endl; if (this != &s) { Person:: operator=(s); } }
??析構(gòu)函數(shù)
由于編譯器會將析構(gòu)函數(shù)的名字處理成destructor,因此派生類和基類的析構(gòu)函數(shù)會構(gòu)成隱藏關(guān)系,故若要派生類要調(diào)用基類的析構(gòu)函數(shù),那么需要顯式調(diào)用,但是編譯器會默認(rèn)在派生類的析構(gòu)函數(shù)調(diào)用結(jié)束后調(diào)用基類的析構(gòu)函數(shù),這樣就析構(gòu)兩次了。
~Person() { cout << "~Person()" << endl; } ~Student() { Person:: ~Person(); cout << "~Student()" << endl; }
在派生類中,基類的析構(gòu)函數(shù)會被隱藏,雖然它們這里的名字不同,但是為了實現(xiàn)多態(tài), 它們都會被編譯器重命名為destructor。在調(diào)用子類的構(gòu)造函數(shù)時,我們是先調(diào)用父類的構(gòu)造函數(shù),后對子類的成員進(jìn)行構(gòu)造。由先構(gòu)造后析構(gòu)的順序,所以我們是在析構(gòu)函數(shù)當(dāng)中析構(gòu)子類的資源,析構(gòu)函數(shù)調(diào)用完后編譯器自動幫我們調(diào)用父類的析構(gòu)函數(shù)。
6.友元與靜態(tài)成員
??1.友元
友元關(guān)系是不會繼承的,如果子類要使用父類的友元,則子類自己也要將其定義為友元。
??2.靜態(tài)成員
基類定義了static靜態(tài)成員,無論繼承了多少次,派生了多少子類,靜態(tài)成員在這整個繼承體系中有且只有一個。靜態(tài)成員不再單獨屬于某一個類亦或者是某一個對象,而是屬于這一整個繼承體系。
7.菱形繼承與虛繼承
首先簡單介紹下單繼承、多繼承的概念
單繼承:一個子類只有一個直接父類時稱這個繼承關(guān)系為單繼承
多繼承:一個子類有兩個或以上直接父類時稱這個繼承關(guān)系為多繼承
菱形繼承:菱形繼承是多繼承的一種特殊情況,下面簡單舉例介紹下菱形繼承及其帶來的二義性問題:
class Human { public: int _age; }; class Student : public Human { public: int _stuNum; }; class Teacher : public Human { public: int _teaNum; }; class Assistant : public Teacher, public Student { };
哦豁?。?!菱形繼承這個關(guān)系感受到了吧?。?!
??直接講下這個菱形繼承帶來的二義性問題:
按照道理來說,各個類的大小應(yīng)該是這樣的。human類4個字節(jié),teacher和student都是8個字節(jié),而assistant是12個字節(jié),但是實際上assistant卻是16字節(jié)。
??為什么assistant會有16字節(jié)?
這就是菱形繼承的數(shù)據(jù)冗余和二義性問題的體現(xiàn)。
這里的teacher和student都從human中繼承了相同的成員 _age。但是assistant再從teacher和student繼承時,就分別把這兩個 _age都給繼承了過來,導(dǎo)致這里有了兩個一樣的成員。
在這樣的情況下,后續(xù)想給 _age賦值,也會被編譯器提示指示不明確,報錯。
??菱形繼承的二義性是很致命的問題,如何解決呢?
虛繼承,在腰部的類繼承時添加virtual關(guān)鍵字。
class Student : virtual public Human { public: int _stuNum; }; class Teacher : virtual public Human { public: int _teaNum; }; class Assistant : public Teacher, public Student { };
這次,二義性問題解決了,teacher和student都是12個字節(jié),而assistant是20個字節(jié)。
想知道為什么?點這里:從內(nèi)存角度看待虛繼承
??簡單小結(jié)一下
1.可以看到?jīng)]有虛繼承的情況下,Assitant中的成員連續(xù)排列出現(xiàn)了Teacher和Student中的_age是兩個不同的值,但實際上一個人不會有兩個年齡,所以這就出現(xiàn)了數(shù)據(jù)冗余。
2.這里多出來的8個字節(jié),其實是兩個虛基表指針。因為這里Human中的 _age是 teacher和 student共有的,所以為了能夠方便處理,在內(nèi)存中分布的時候,就會把這個共有成員 _age放到對象組成的最末尾的位置。然后通過了Teacher和Student的兩個指針,指向的一張表。這兩個指針叫虛基表指針,這兩個表叫虛基表。虛基表中存的偏移量。通過偏移量可以找到下面的Human。由此可見,使用了虛擬繼承后,就可以解決菱形繼承導(dǎo)致的問題。
??為什么Teacher、Student需要去找屬于自己的 _age?
基類與派生類的賦值轉(zhuǎn)換時,需要進(jìn)行切片。
int main() { Assistant a; Teacher t = a; Student s = a; return 0; }
當(dāng)把對象a賦值給t和s的時候,因為他們互相沒有對方的 _stuNum和 _teaNum,所以他們需要進(jìn)行對象的切割,但是又因為 _age存放在對象的最尾部,所以只有知道了自己的偏移量,才能夠成功的在切割了沒有的元素時,還能找到自己的 _age。
8.繼承和組合
??繼承是一種復(fù)用的方式,但不是唯一方式!
1.public繼承是一種is-a的關(guān)系,就是基類是一個大類,而派生類則是這個大類中細(xì)分出來的一個子類,但是他們本質(zhì)上其實是一種東西。
2.組合是一種has-a的關(guān)系,就是一種包含關(guān)系,比如對象a是對象b的成員,那么他們的關(guān)系就是對象b的組成中包含了對象a,對象a是對象b中的一部分,對象b包含對象a。
3.繼承方式的復(fù)用常稱之為白箱復(fù)用,在繼承方式中,基類的內(nèi)部細(xì)節(jié)對子類可見,這一定程度上破壞了基類的封裝,伴隨著基類的改變,對派生類的改變很大。并且兩者依賴關(guān)系強(qiáng),耦合度大。
4.對象組合式繼承之外的復(fù)用選擇,對象組合要求被組合對象提供良好的接口定義。這種復(fù)用稱之為黑箱復(fù)用,對象的內(nèi)部實現(xiàn)細(xì)節(jié)是不可見的。耦合度低。
實際工程中能用繼承和組合就用組合,組合的耦合度低,代碼的維護(hù)性好,但是繼承在有些關(guān)系就適合用繼承就用繼承,并且要實現(xiàn)多態(tài)就一定要用繼承。
到此這篇關(guān)于C++ 繼承的范例講解的文章就介紹到這了,更多相關(guān)C++ 繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言16進(jìn)制與ASCII字符相互轉(zhuǎn)換
大家好,本篇文章主要講的是C語言16進(jìn)制與ASCII字符相互轉(zhuǎn)換,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01C++可調(diào)用對象callable object深入分析
所謂的callable object,表示可以被某種方式調(diào)用其某些函數(shù)的對象。它可以是:一個函數(shù)、一個指向成員函數(shù)的指針、一個函數(shù)對象,該對象擁有operator()、一個lambda表達(dá)式,嚴(yán)格的說它是一種函數(shù)對象2022-08-08