您好,登錄后才能下訂單哦!
這篇“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”文章吧。
繼承:繼承機(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ù)用。
語(yǔ)法:
說(shuō)明: 派生類會(huì)將基類的成員變量和成員函數(shù)都繼承下來(lái),但是訪問(wèn)限定符會(huì)根據(jù)繼承方式而發(fā)生變化。
繼承方式有三種:
public繼承
protected繼承
private繼承
訪問(wèn)限定符:
public訪問(wèn)
protected訪問(wèn)
private訪問(wèn)
繼承基類成員的訪問(wèn)方式的變化:
類成員/繼承方式 | public繼承 | protected繼承 | private繼承 |
---|---|---|---|
基類的public成員 | 派生類的public成員 | 派生類的protected成員 | 派生類的private成員 |
基類的protected成員 | 派生類的protected成員 | 派生類的protected成員 | 派生類的private成員 |
基類的private成員 | 派生類中不可見(jiàn) | 派生類中不可見(jiàn) | 派生類中不可見(jiàn) |
總結(jié):
基類的private成員在派生類中都是不可見(jiàn)的,這里的不可見(jiàn)是指基類的私有成員還是被繼承到了派生類對(duì)象中,但是語(yǔ)法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問(wèn)它。
基類成員在父類中的訪問(wèn)方式=min(成員在基類的訪問(wèn)限定符,繼承方式),public>protected>private。
一般會(huì)把基類中不想讓類外訪問(wèn)的成員設(shè)置為protecd成員,不讓類外訪問(wèn),但是讓派生類可以訪問(wèn)。
派生類對(duì)象會(huì)通過(guò) “切片” 或 “切割” 的方式賦值給基類的對(duì)象、指針或引用。但是基類對(duì)象不能賦值給派生類對(duì)象。
實(shí)例演示:
class Person { public: Person(const char* name = "") :_name(name) {} void Print() { cout << "name:" << _name << " age:" << _age << endl; } protected: string _name = ""; int _age = 1; }; class Student : public Person { public: Student() :Person("xiaoming") {} void Print() { cout << "name:" << _name << " age:" << _age << " _stuid:" << _stuid << " _major:" << _major << endl; } private: int _stuid = 0;// 學(xué)號(hào) int _major = 0;// 專業(yè) }; int main() { Student s; // 子類對(duì)象可以賦值給父類的對(duì)象、指針和引用 反過(guò)來(lái)不行 // Student對(duì)象通過(guò) “切片” 或 “切割” 的方式進(jìn)行賦值 Person p1 = s; Person* p2 = &s; Person& p3 = s; p1.Print(); p2->Print(); p3.Print(); // 基類的指針可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針 Student* ps = (Student*)p2; ps->Print(); return 0; }
總結(jié):
派生類對(duì)象可以“切片”或“切割”的方式賦值給基類的對(duì)象,基類的指針或基類的引用,就是把基類的那部分切割下來(lái)。
基類對(duì)象不能給派生類對(duì)象賦值。
基類的指針可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針。但必須是基類的指針指向派生類的對(duì)象才是安全的,因?yàn)槿绻愂嵌鄳B(tài)類型,會(huì)引發(fā)多態(tài)。
在繼承體系中,基類和派生類對(duì)象都有獨(dú)立的作用域,子類中的成員(成員變量和成員函數(shù))會(huì)對(duì)父類的同名成員進(jìn)行隱藏,也叫重定義。
實(shí)例演示:
class Person { public: Person(const char* name = "") :_name(name) {} void Print() { cout << "name:" << _name << " age:" << _age << endl; } protected: string _name = ""; int _age = 1; }; class Teacher : public Person { public: void Print() { cout << "name:" << _name << " age:" << _age << " jobid:" << _jobid << endl; } private: int _jobid = 0;// 工號(hào) }; int main() { Teacher t; t.Print(); t.Person::Print();// 子類會(huì)隱藏(重定義)父類的同名成員(同名函數(shù)或同名成員變量) 可以通過(guò)指定域作用限定符訪問(wèn) return 0; }
代碼運(yùn)行結(jié)果如下:
得出結(jié)論: 子類中的成員(成員變量和成員函數(shù))會(huì)對(duì)父類的同名成員進(jìn)行隱藏,如果相要訪問(wèn)父類的同名成員,必須指定類域訪問(wèn)。
看下面一個(gè)小問(wèn)題: 請(qǐng)問(wèn)A中的fun函數(shù)和B中的fun函數(shù)是構(gòu)成重載還是隱藏?
class A { public: void fun() { cout << "func()" << endl; } }; class B : public A { public: void fun(int i) { A::fun(); cout << "func(int i)->" << i << endl; } }; void Test() { B b; b.fun(10); };
答案: 兩個(gè)函數(shù)在不同的作用域,不可能構(gòu)成重載。因?yàn)闃?gòu)成重載的條件是兩個(gè)函數(shù)必須在同一作用域,而隱藏是要求在基類和派生類不同作用域的,所以這里同名成員是構(gòu)造隱藏。
C++中的每個(gè)對(duì)象中會(huì)有6個(gè)默認(rèn)成員函數(shù)。默認(rèn)的意思就是我們不寫,編譯器會(huì)生成一個(gè)。那么在繼承中,子類的默認(rèn)成員函數(shù)是怎么生成的呢?
先看下面一個(gè)例子:
class Person { public: Person(const char* name = "", int age = 1) :_name(name) ,_age(age) { cout << "Person()" << endl; } Person(const Person& p) :_name(p._name) , _age(p._age) { cout << "Person(const Person& p)" << endl; } Person& operator=(const Person& p) { _name = p._name; _age = p._age; cout << "Person& operator=(const Person& p)" << endl; return *this; } void Print() { cout << "name:" << _name << " age:" << _age << endl; } ~Person() { cout << "~Person()" << endl; } protected: string _name; int _age; }; class Student : public Person { public: Student(const char* name, int age, int stuid = 0) :Person(name, age)// 此處調(diào)用父類的構(gòu)造函數(shù)堆繼承下來(lái)的成員進(jìn)行初始化,不謝的話,編譯器調(diào)用父類的默認(rèn)構(gòu)造函數(shù) , _stuid(stuid) { cout << "Student()" << endl; } Student(const Student& s) :Person(s)// 子類對(duì)象可以傳給父類的對(duì)象、指針或引用 ,_stuid(s._stuid) { cout << "Student(const Student& s)" << endl; } Student& operator=(const Student& s) { cout << "Student& operator=(const Student& s)" << endl; if (this != &s) { Person::operator=(s);// 先完成基類的復(fù)制 _stuid = s._stuid; } return *this; } void Print() { cout << "name:" << _name << " age:" << _age << " _stuid:" << _stuid << endl; } ~Student() { // 基類和派生類的析構(gòu)函數(shù)的函數(shù)名都被編譯器處理成了destruction,構(gòu)成隱藏,是一樣指定域訪問(wèn) //Person::~Person();// 不需要顯示調(diào)用 編譯器會(huì)自動(dòng)先調(diào)用派生類的析構(gòu)函數(shù),然后調(diào)用基類的析構(gòu)函數(shù) cout << "~Student()" << endl; } private: int _stuid;// 學(xué)號(hào) };
測(cè)試1:構(gòu)造函數(shù)和析構(gòu)函數(shù)
void test1() { Student s("小明",18,10); }
代碼運(yùn)行結(jié)果如下:
總結(jié)1: 子類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類沒(méi)有默認(rèn)構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。子類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類的成員。不需要顯示調(diào)用。這里子類和父類的析構(gòu)函數(shù)的函數(shù)名會(huì)被編譯器處理成destructor,這樣兩個(gè)函數(shù)構(gòu)成隱藏。
測(cè)試2:拷貝構(gòu)造函數(shù)
void test2() { Student s1("小明", 18, 10); Student s2(s1); }
代碼運(yùn)行結(jié)果如下:
總結(jié)2: 子類的拷貝構(gòu)造必須代用父類的拷貝構(gòu)造完成父類成員的拷貝。
測(cè)試3:operator=
結(jié)論3: 子類的operator=必須調(diào)用基類的operator完成基類的賦值。
思考
如何設(shè)計(jì)一個(gè)不能被繼承的類? 把該類的構(gòu)造函數(shù)設(shè)為私有。如果基類的構(gòu)造函數(shù)是私有,那么派生類不能調(diào)用基類的構(gòu)造函數(shù)完成基類成員的初始化,則無(wú)法進(jìn)行構(gòu)造。所以這樣設(shè)計(jì)的類不可以被繼承。(后面還會(huì)將加上final關(guān)鍵字的類也不可以被繼承)
總結(jié):
子類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類沒(méi)有默認(rèn)構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
子類的拷貝構(gòu)造必須代用父類的拷貝構(gòu)造完成父類成員的拷貝。
子類的operator=必須調(diào)用基類的operator完成基類的賦值。
子類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類的成員。不需要顯示調(diào)用。
子類對(duì)象會(huì)先調(diào)用父類的構(gòu)造在調(diào)用子類的構(gòu)造。
子類對(duì)象會(huì)先析構(gòu)子類的析構(gòu)再調(diào)用父類的析構(gòu)。
友元關(guān)系不能被繼承。也就是說(shuō)基類的友元不能夠訪問(wèn)子類的私有和保護(hù)成員。
基類定義的static靜態(tài)成員,存在于整個(gè)類中,不屬于某個(gè)類,無(wú)論右多少個(gè)派生類,都這有一個(gè)static成員。
實(shí)例演示:
class Person { public: Person() { ++_count; } // static成員存在于整個(gè)類 無(wú)論實(shí)例化出多少對(duì)象,都只有一個(gè)static成員實(shí)例 static int _count; }; int Person::_count = 0; class Student :public Person { public: int _stuid; }; int main() { Student s1; Student s2; Student s3; // Student()._count = 10; cout << "人數(shù):" << Student()._count - 1 << endl; return 0; }
代碼運(yùn)行結(jié)果如下:
單繼承: 一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承。
多繼承: 一個(gè)子類有兩個(gè)或以上的直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承。
菱形繼承: 多繼承的一種特殊情況。
多繼承帶來(lái)的問(wèn)題: 子類會(huì)得到兩份BenZ的數(shù)據(jù),會(huì)造成數(shù)據(jù)冗余和二義性。
為了解決菱形繼承帶來(lái)的數(shù)據(jù)冗余和二義性的問(wèn)題,C++提出來(lái)虛擬繼承這個(gè)概念。虛擬繼承可以解決前面的問(wèn)題,在繼承方式前加椰果virtual的關(guān)鍵字即可。
class Person { public: string _name; }; // 不要在其他地方去使用。 class Student : virtual public Person { public: int _num; //學(xué)號(hào) }; class Teacher : virtual public Person { public: int _id; // 職工編號(hào) }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修課程 };
先看下面一串代碼:
class A { public: int _a; }; class B :virtual public A { public: int _b; }; class C :virtual 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 = 4; d._c = 5; d._d = 6; return 0; }
我們通過(guò)內(nèi)存窗口查看它的對(duì)象模型:
原理: 從上圖可以看出,A對(duì)象同時(shí)屬于B和C,B和C中分別存放了一個(gè)指針,這個(gè)指針叫虛基表指針,分別指向的兩張表,叫虛基表,虛基表中存的是偏移量,B和C通過(guò)偏移量就可以找到公共空間(存放A對(duì)象的位置)。
總結(jié)一下幾點(diǎn):
組合和繼承都屬于類層次的復(fù)用。
public繼承是一種is-a的關(guān)系。也就是說(shuō)每個(gè)派生類對(duì)象都是一個(gè)基類對(duì)象-。
組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個(gè)B對(duì)象中都有一個(gè)A對(duì)象。
優(yōu)先使用對(duì)象組合,而不是類繼承 。
繼承允許你根據(jù)基類的實(shí)現(xiàn)來(lái)定義派生類的實(shí)現(xiàn)。這種通過(guò)生成派生類的復(fù)用通常被稱為白箱復(fù)用。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼承方式中,基類的內(nèi)部細(xì)節(jié)對(duì)子類可見(jiàn) 。繼承一定程度破壞了基類的封裝,基類的改變,對(duì)派生類有很大的影響。派生類和基類間的依賴關(guān)系很強(qiáng),耦合度高。
對(duì)象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過(guò)組裝或組合對(duì)象來(lái)獲得。對(duì)象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為黑箱復(fù)用,因?yàn)閷?duì)象的內(nèi)部細(xì)是不可見(jiàn)的。對(duì)象只以“黑箱”的形式出現(xiàn)。 組合類之間沒(méi)有很強(qiáng)的依賴關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類被封裝。
實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過(guò)繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類之間的關(guān)系可以用繼承,可以用組合,就用組合。
C++的缺陷之一:
多繼承就是一個(gè)。多繼承會(huì)帶來(lái)菱形繼承,菱形繼承又會(huì)帶來(lái)數(shù)據(jù)冗余和二義性,為了解決這個(gè)問(wèn)題,又引入了虛擬繼承。進(jìn)而導(dǎo)致C++的底層結(jié)構(gòu)對(duì)象模型非常復(fù)雜,這樣會(huì)帶來(lái)一定的損失。所以說(shuō)盡量不要設(shè)計(jì)出菱形繼承。
以上就是關(guān)于“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。