C++繼承的賦值轉(zhuǎn)換與菱形虛擬繼承深入詳解
一、繼承的概念及定義
繼承是面向?qū)ο笕筇匦灾弧?/p>
1.1、繼承的概念
繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用。繼承是類設(shè)計(jì)層次的復(fù)用。
1.2、繼承的定義
繼承的語(yǔ)法:class 子類 : 繼承方式 父類
繼承方式:
- 共有繼承
- 私有繼承
- 保護(hù)繼承
基類private成員在派生類中無(wú)論以什么方式繼承都是不可見的。這里的不可見是指基類的私有成員還是被繼承到了派生類對(duì)象中,但是語(yǔ)法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問(wèn)它。
二、基類和派生類對(duì)象賦值轉(zhuǎn)換
- 派生類對(duì)象可以賦值給基類的對(duì)象/基類的指針/基類的引用。這里有個(gè)形象的說(shuō)法叫切片或者切割。寓意把派生類中父類那部分切來(lái)賦值過(guò)去。
- 基類對(duì)象不能賦值給派生類對(duì)象。
- 基類的指針或者引用可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對(duì)象時(shí)才是安全的。
class person{ protected: string _name; int _age; }; class student :public person { public: int _No; }; void test01() { student sobj; //1.子類對(duì)象可以賦值給父類對(duì)象/指針/引用 person pobj = sobj; person* pp = &sobj; person& rp = sobj; //2.基類對(duì)象不可以賦值給派生類對(duì)象 //sobj = pobj; //3.基類的指針可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針 pp = &sobj; student* ps1 = (student*)pp;//這種情況是可以的 ps1->_No = 10; pp = &pobj; student* ps2 = (student*)pp;//這種情況轉(zhuǎn)換時(shí)雖然可以,但存在越界訪問(wèn)的問(wèn)題 ps2->_No = 10; }
三、繼承中的作用域
3.1、繼承同名成員處理方式
??問(wèn)題:當(dāng)子類與父類出現(xiàn)同名的成員,如何通過(guò)子類對(duì)象,訪問(wèn)到子類或父類中同名的數(shù)據(jù)呢?
class Base { public: Base(){ m_A = 100; } void func(){ cout << "Base - func()調(diào)用" << endl; } void func(int a){ cout << "Base - func(int a)調(diào)用" << endl; } public: int m_A; }; class Son : public Base { public: Son(){ m_A = 200; } //當(dāng)子類與父類擁有同名的成員函數(shù),子類會(huì)隱藏父類中所有版本的同名成員函數(shù) //如果想訪問(wèn)父類中被隱藏的同名成員函數(shù),需要加父類的作用域 void func() { cout << "Son - func()調(diào)用" << endl; } public: int m_A; }; void test01() { Son s; cout << "Son下的m_A = " << s.m_A << endl; cout << "Base下的m_A = " << s.Base::m_A << endl; s.func(); s.Base::func(); s.Base::func(10); }
??????總結(jié):
- 子類對(duì)象可以直接訪問(wèn)到子類中同名成員
- 子類對(duì)象加作用域可以訪問(wèn)到父類同名成員
- 當(dāng)子類與父類擁有同名的成員函數(shù),子類會(huì)隱藏父類中同名成員函數(shù),加作用域可以訪問(wèn)到父類中同名函數(shù)。
- 當(dāng)然父類對(duì)象隨便調(diào)用父類成員。
注:子類和父類中有同名成員時(shí)構(gòu)成隱藏關(guān)系,也叫重定義。需要注意的是,如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
3.2、繼承同名靜態(tài)成員處理方式
??:問(wèn)題:繼承中同名的靜態(tài)成員在子類對(duì)象上如何進(jìn)行訪問(wèn)?
靜態(tài)成員和非靜態(tài)成員出現(xiàn)同名,處理方式一致:
- 子類對(duì)象訪問(wèn)子類同名成員 直接訪問(wèn)即可
- 子類對(duì)象訪問(wèn)父類同名成員 需要加作用域
class Base { public: static void func() { cout << "Base - static void func()" << endl; } static void func(int a) { cout << "Base - static void func(int a)" << endl; } static int m_A; }; int Base::m_A = 100; class Son : public Base { public: static void func() { cout << "Son - static void func()" << endl; } static int m_A; }; int Son::m_A = 200; //同名成員屬性 void test01() { //通過(guò)對(duì)象訪問(wèn) cout << "通過(guò)對(duì)象訪問(wèn): " << endl; Son s; cout << "Son 下 m_A = " << s.m_A << endl; cout << "Base 下 m_A = " << s.Base::m_A << endl; //通過(guò)類名訪問(wèn) cout << "通過(guò)類名訪問(wèn): " << endl; cout << "Son 下 m_A = " << Son::m_A << endl; cout << "Base 下 m_A = " << Son::Base::m_A << endl; } //同名成員函數(shù) void test02() { //通過(guò)對(duì)象訪問(wèn) cout << "通過(guò)對(duì)象訪問(wèn): " << endl; Son s; s.func(); s.Base::func(); cout << "通過(guò)類名訪問(wèn): " << endl; Son::func(); Son::Base::func(); //出現(xiàn)同名,子類會(huì)隱藏掉父類中所有同名成員函數(shù),需要加作作用域訪問(wèn) Son::Base::func(100); }
總結(jié):同名靜態(tài)成員處理方式和非靜態(tài)處理方式一樣,只不過(guò)有兩種訪問(wèn)的方式(通過(guò)對(duì)象 和 通過(guò)類名)
3.3、繼承與友元
友元關(guān)系不可以繼承,也就是說(shuō)基類的友元不要可以訪問(wèn)子類的私有成員和保護(hù)成員。
(就好比說(shuō)爸爸的朋友不一定是我的朋友)
3.4、繼承與靜態(tài)成員
基類定義了static靜態(tài)成員,則整個(gè)繼承體系只有這一個(gè)成員(我們知道靜態(tài)成員是整個(gè)類共享的),無(wú)論派生出多少個(gè)子類,都只有這么一個(gè)static成員。
class person { public: person() { _count++; } protected: string _name; public: static int _count;//統(tǒng)計(jì)人數(shù) }; int person::_count = 0; class student:public person { protected: int _stuNum; }; class graduate :public student { protected: string course; }; void test() { student s1; student s2; student s3; graduate s4; cout << "人數(shù)" << person::_count << endl; student::_count = 0; cout << "人數(shù)" << person::_count << endl; }
人數(shù)4
人數(shù)0
請(qǐng)按任意鍵繼續(xù). .
代碼解釋:因?yàn)樽宇悓?duì)象構(gòu)造是會(huì)調(diào)用基類的構(gòu)造函數(shù),所以每實(shí)例化一個(gè)子類對(duì)象都會(huì)調(diào)用一次基類構(gòu)造,從而_count++,并且靜態(tài)成員是整個(gè)類共享的,所以無(wú)論哪個(gè)子類都可修改?。?!
四、派生類的默認(rèn)成員函數(shù)
6個(gè)默認(rèn)成員函數(shù),“默認(rèn)"的意思就是指我們不寫,編譯器會(huì)變我們自動(dòng)生成一個(gè),那么在派生類中,這幾個(gè)成員函數(shù)是如何生成的呢?
- 派生類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員。如果基類沒(méi)有默認(rèn)的構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
- 派生類的拷貝構(gòu)造函數(shù)必須調(diào)用基類的拷貝構(gòu)造完成基類的拷貝初始化。
- 派生類的operator=必須要調(diào)用基類的operator=完成基類的復(fù)制。
- 派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類成員。因?yàn)檫@樣才能保證派生類對(duì)象先清理派生類成員再清理基類成員的順序。
- 派生類對(duì)象初始化先調(diào)用基類構(gòu)造再調(diào)派生類構(gòu)造。
- 派生類對(duì)象析構(gòu)清理先調(diào)用派生類析構(gòu)再調(diào)基類的析構(gòu)。
class person { public: person(const char* name = "pxl") :_name(name) {} person(const person& p) :_name(p._name) {} person& operator=(const person& p) { if (this != *p){ _name = p._name; } return *this; } ~person() {} protected: string _name; }; class student :public person { public: student(const char* name, int num) :person(name)//顯示調(diào)用基類的構(gòu)造函數(shù)初始化基類成員 , _num(num) {} student(const student& s) :person(s)//注意這里有個(gè)隱式的切片操作 person& p = s; , _num(s._num) {} student& operator=(const student& s) { if (this != &s){ person::operator=(s);//調(diào)用基類的operator=完成基類的賦值 _num = s._num; } return *this; } ~student() { cout << "~student()" << endl; //注意這里會(huì)自動(dòng)調(diào)用父類析構(gòu) } protected: int _num; }; void test() { student s1("ppp", 20); student s2(s1); student s3("xxx", 30); s1 = s3; }
??留意代碼中注釋部分!
五、復(fù)雜菱形繼承及菱形虛擬繼承
5.1、繼承分類
單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱為單繼承
多繼承:一個(gè)子類有兩個(gè)或者兩個(gè)以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承
菱形繼承:兩個(gè)派生類繼承同一個(gè)基類,又有某個(gè)類同時(shí)繼承者兩個(gè)派生類。菱形繼承帶來(lái)的主要問(wèn)題是子類繼承兩份相同的數(shù)據(jù),導(dǎo)致資源浪費(fèi)以及毫無(wú)意義。
利用虛繼承可以解決菱形繼承問(wèn)題
????????????????????????????????????????????????????????
對(duì)于菱形繼承的二義性問(wèn)題,我們可以在訪問(wèn)的時(shí)候加上類域,這樣是可以解決的,但是數(shù)據(jù)冗余無(wú)法解決。所以下面引入虛擬繼承!
5.2、虛擬繼承解決菱形繼承問(wèn)題原理
為了研究虛擬繼承原理,我們給出一個(gè)簡(jiǎn)單的菱形繼承體系,再借助內(nèi)存窗口觀察對(duì)象成員模型。
class A{ public: int _a; }; class B:public A { public: int _b; }; class C :public A { public: int _c; }; class D :public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; system("pause"); return 0; }
如圖是菱形繼承的內(nèi)存對(duì)象成員模型,可以看出來(lái)數(shù)據(jù)冗余!??!
下面是菱形虛擬繼承的內(nèi)存對(duì)象成員模型:
這里可以分析出D對(duì)象將A放在了對(duì)象組成的最下面,這個(gè)A同時(shí)屬于B和C,那么B和C如何去找到公共的A呢?
這里通過(guò)B和C的兩個(gè)指針,指向一張表。這兩個(gè)指針叫虛基表指針,這兩個(gè)表叫虛基表,虛基表中存的是偏移量。通過(guò)偏移量可以找到下面的A。
到此這篇關(guān)于C++繼承的賦值轉(zhuǎn)換與菱形虛擬繼承深入詳解的文章就介紹到這了,更多相關(guān)C++繼承的賦值轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一篇文章帶你了解C語(yǔ)言的選擇結(jié)構(gòu)
這篇文章主要為大家介紹了C語(yǔ)言的選擇結(jié)構(gòu),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01Unity3D實(shí)現(xiàn)經(jīng)典小游戲Pacman
這篇文章主要介紹了基于Unity3D制作一做個(gè)經(jīng)典小游戲Pacman,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Unity3D有一定的幫助,感興趣的小伙伴可以了解一下2021-12-12C語(yǔ)言用棧模擬實(shí)現(xiàn)隊(duì)列問(wèn)題詳解
本片文章帶你分析如何用兩個(gè)棧,并且只使用棧的基本功能來(lái)模擬實(shí)現(xiàn)隊(duì)列,其中同樣只實(shí)現(xiàn)隊(duì)列的基本功能,感興趣的朋友來(lái)看看吧2022-04-04在c和c++中實(shí)現(xiàn)函數(shù)回調(diào)
如何在c和c++中實(shí)現(xiàn)函數(shù)回調(diào)呢?現(xiàn)在小編就和大家分享一下在c/c++中實(shí)現(xiàn)函數(shù)回調(diào)的示例代碼,需要的朋友可以參考下2013-07-07C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的掃雷游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12C語(yǔ)言通過(guò)三種方法實(shí)現(xiàn)屬于你的通訊錄
本文將實(shí)現(xiàn)一個(gè)通訊錄,來(lái)實(shí)現(xiàn)人員的增刪插改功能。文中通過(guò)三種形式來(lái)實(shí)現(xiàn)用戶的增刪插改,其實(shí)也就是一點(diǎn)點(diǎn)的優(yōu)化版本,從靜態(tài)的實(shí)現(xiàn),到動(dòng)態(tài)的實(shí)現(xiàn),最后以文件的形式來(lái)完成,請(qǐng)大家和我一起往下看吧2022-11-11