前言
大家好吖,歡迎來到 YY 滴C++系列 ,熱烈歡迎! 本章主要內(nèi)容面向接觸過C++的老鐵
主要內(nèi)容含:
歡迎訂閱 YY滴C++專欄!更多干貨持續(xù)更新!以下是傳送門!
- YY的《C++》專欄
- YY的《C++11》專欄
- YY的《Linux》專欄
- YY的《數(shù)據(jù)結(jié)構(gòu)》專欄
- YY的《C語言基礎(chǔ)》專欄
- YY的《初學(xué)者易錯點(diǎn)》專欄
- YY的《小小知識點(diǎn)》專欄
一.繼承&復(fù)用&組合的區(qū)別
1)函數(shù)復(fù)用與繼承區(qū)別
函數(shù)復(fù)用與繼承區(qū)別:
- 繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能, 這樣產(chǎn)生新的類,稱派生類 。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計的層次結(jié)構(gòu),體現(xiàn)了由簡單到復(fù)雜的認(rèn)知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類 設(shè)計層次 的復(fù)用。
2)復(fù)用的分類
組合與繼承:
- public繼承是一種is-a的關(guān)系。也就是說每個派生類對象 都是一個基類對象:相當(dāng)于[人]與[學(xué)生]&[老師]的關(guān)系
- 組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個B對象中 都有一個A對象:相當(dāng)于[手]與[人]的關(guān)系
- 優(yōu)先使用對象組合,而不是類繼承
- 實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過繼承也有用武之地的,有
些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類之間的關(guān)系可以用
繼承,可以用組合,就用組合
[1]白箱復(fù)用——繼承
- 繼承允許你根據(jù)基類的實(shí)現(xiàn)來定義派生類的實(shí)現(xiàn)。這種通過生成派生類的復(fù)用通常被稱 為白箱復(fù)用(white-box reuse)。術(shù)語“白箱”是相對可視性而言:在繼承方式中,基類的 內(nèi)部細(xì)節(jié)對子類可見。 繼承一定程度破壞了基類的封裝 ,基類的改變,對派生類有很 大的影響。派生類和基類間的依賴關(guān)系很強(qiáng), 耦合度高。
[2]黑箱復(fù)用——組合(優(yōu)先)
- 對象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過組裝或組合對象來獲得。 對象組合要求被組合的對象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為 黑箱復(fù)用(black-box reuse),因?yàn)閷ο蟮膬?nèi)部細(xì)節(jié)是不可見的。對象只以“黑箱”的形式出現(xiàn)。組合類之間沒有很強(qiáng)的依賴關(guān)系,耦合度低。優(yōu)先使用對象組合有助于你保持每個類被封裝.
二.繼承的基本格式與繼承以后的訪問方式變化(基類成員)
1)基本格式
2)三種繼承方式
- 繼承方式分為三種:public繼承,protect繼承,private繼承
- 保護(hù)訪問限定符專門為繼承而產(chǎn)生:基類private成員在派生類中是不能被訪問 ,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為 protected ??梢钥闯霰Wo(hù)成員限定符是因繼承才出現(xiàn)的。
3)在派生類中不可見
- 基類private成員在派生類中無論以什么方式繼承都是 不可見的 。這里的不可見是指基類的私有成員還是被繼承到了派生類對象中,但是語法上限制派生類對象不管在類里面還是類外面都不能去訪問它。
- 基類的保護(hù)成員 在派生類中仍然是保護(hù)成員,也無法通過派生類的對象來直接訪問。
4)基類成員經(jīng)過不同繼承以后分別到派生類的什么作用域中【訪問方式變化】
記憶與理解: 文章來源:http://www.zghlxwxcb.cn/news/detail-791114.html
- 權(quán)限大?。簆ublic > protect > private
- 基類中的public成員,經(jīng)過什么繼承,就到派生域的什么作用域中
- 【以權(quán)限小的為主】基類中的protect成員,權(quán)限小于public:經(jīng)過public繼承還是到protect作用域中,其權(quán)限又大于private,繼承,經(jīng)過private繼承后到private作用域中
- 基類中的private成員,經(jīng)過任何繼承都在派生類中不可見
![]()
5)struct和class的默認(rèn)繼承方式
- 使用關(guān)鍵字class時默認(rèn)的繼承方式是private
- 使用struct時默認(rèn)的繼承方式是public
- 不過最好顯示的寫出繼承方式
6)實(shí)際運(yùn)用中一般使用都是public繼承的原因
- 在實(shí)際運(yùn)用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承, 因?yàn)閜rotetced/private繼承下來的成員都只能在派生類的類里面使用 ,實(shí)際中擴(kuò)展維護(hù)性不強(qiáng)
三.基類和派生類對象賦值轉(zhuǎn)換【切片概念】
- 派生類對象 可以賦值給 基類的對象 / 基類的指針 / 基類的引用。這里有個形象的說法叫切片
或者切割。寓意 把派生類中父類那部分 切來賦值過去- 基類對象不能賦值給派生類對象
- 基類的指針或者引用可以通過 強(qiáng)制類型轉(zhuǎn)換 賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對象時才是安全的
![]()
class Person
{
protected :
string _name; // 姓名
string _sex; // 性別
int _age; // 年齡
};
class Student : public Person
{
public :
int _No ; // 學(xué)號
};
void Test ()
{
Student sobj ;
// 1.子類對象可以賦值給父類對象/指針/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基類對象不能賦值給派生類對象
sobj = pobj;
// 3.基類的指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針
pp = &sobj
Student* ps1 = (Student*)pp; // 這種情況轉(zhuǎn)換時可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 這種情況轉(zhuǎn)換時雖然可以,但是會存在越界訪問的問題
ps2->_No = 10;
}
四.繼承中的【隱藏】
- 在繼承體系中基類和派生類都有獨(dú)立的作用域。
- 子類和父類中有 同名成員, 類成員將屏蔽父類對同名成員的直接訪問 ,這種情況叫 隱藏, 也叫重定義。(在子類成員函數(shù)中,可以使用 基類::基類成員 顯示訪問)
- 需要注意的是如果是 成員函數(shù) 的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
- 注意在實(shí)際中在繼承體系里面最好不要定義同名的成員。
五.派生類的默認(rèn)成員函數(shù)生成機(jī)制
6個默認(rèn)成員函數(shù),“默認(rèn)”的意思就是指我們不寫,編譯器會變我們自動生成一個,那么在派生類
中,這幾個成員函數(shù)是如何生成的呢?文章來源地址http://www.zghlxwxcb.cn/news/detail-791114.html
- 派生類的構(gòu)造函數(shù)必須調(diào)用 基類 的構(gòu)造函數(shù)初始化基類的那一部分成員。如果基類沒有默認(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ù)會在被調(diào)用完成后自動調(diào)用 基類 的析構(gòu)函數(shù)清理基類成員。因?yàn)檫@樣才能 保證派生類對象先清理派生類成員再清理基類成員的順序。
- 派生類對象初始化先調(diào)用 基類 構(gòu)造再調(diào)派生類構(gòu)造。
- 派生類對象析構(gòu)清理先調(diào)用派生類析構(gòu)再調(diào) 基類 的析構(gòu)。
- 因?yàn)楹罄m(xù)一些場景析構(gòu)函數(shù)需要構(gòu)成重寫,重寫的條件之一是函數(shù)名相同(多態(tài)的條件)。【那么編譯器會對析構(gòu)函數(shù)名進(jìn)行特殊處理,處理成destrutor(),所以父類析構(gòu)函數(shù)不加virtual的情況下,子類析構(gòu)函數(shù)和父類析構(gòu)函數(shù)構(gòu)成隱藏關(guān)系】
六.“友元關(guān)系”不能被繼承
- 友元關(guān)系不能繼承,也就是說基類友元不能訪問子類 私有 和 保護(hù) 成員
//fail
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 學(xué)號
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
void main()
{
Person p;
Student s;
Display(p, s);
}
七.基類定義了static靜態(tài)成員,整個繼承體系里面只有一個這樣的成員
- 基類定義了static靜態(tài)成員,則整個繼承體系里面 只有一個 這樣的成員——即無論派生出多少個子類,都只有一個static成員實(shí)例
class Person
{
public :
Person () {++ _count ;} //派生類會調(diào)用基類的構(gòu)造
protected :
string _name ; // 姓名
public :
static int _count; // 統(tǒng)計人的個數(shù)。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 學(xué)號
};
class Graduate : public Student
{
protected :
string _seminarCourse ; // 研究科目
};
void TestPerson()
{
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;
cout <<" 人數(shù) :"<< Person ::_count << endl; //輸出結(jié)果為4
Student ::_count = 0;
cout <<" 人數(shù) :"<< Person ::_count << endl; //輸出結(jié)果為0
}
八.復(fù)雜的菱形繼承及菱形虛擬繼承
1)菱形繼承
- 在面向?qū)ο笾?,常常存在這樣的事情,一個派生類它有兩個或兩個以上的基類,這種行為稱作多重繼承,示意圖如下:
![]()
- 在多重繼承的基礎(chǔ)上,Class Student 和Class Teacher 存在同名數(shù)據(jù)成員,則對Class Person而言這個同名的數(shù)據(jù)成員容易產(chǎn)生 二義性問題
- 菱形繼承還會產(chǎn)生 數(shù)據(jù)冗余 現(xiàn)象;在Assistant的對象中Person成員會有兩份;
![]()
class Person
{
public :
string _name ; // 姓名
};
class Student : public Person
{
protected :
int _num ; //學(xué)號
};
class Teacher : public Person
{
protected :
int _id ; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修課程
};
void Test ()
{
// 這樣會有二義性無法明確知道訪問的是哪一個
Assistant a ;
a._name = "peter";
// 需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數(shù)據(jù)冗余問題無法解決
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
2)解決菱形繼承問題方法:虛擬繼承
- 虛擬繼承 可以解決菱形繼承的二義性和數(shù)據(jù)冗余的問題。如下圖代碼中 在繼承關(guān)系前加上“virtual” ,在Student和Teacher的繼承Person時使用虛擬繼承,即可解決問題。
- 需要注意的是,虛擬繼承不要在其他地方去使用。
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person //虛擬繼承
{
protected :
int _num ; //學(xué)號
};
class Teacher : virtual public Person //虛擬繼承
{
protected :
int _id ; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修課程
};
void Test ()
{
Assistant a ;
a._name = "peter";
}
3)虛擬繼承解決菱形繼承原理————虛基表&虛基表指針&利用偏移量
【1】虛擬繼承前后的內(nèi)存模型變化
- 虛擬繼承前:
![]()
- 虛擬繼承后:
![]()
- 這里可以分析出D對象中將A放到的了 對象組成的最下面 ,這個A同時屬于B和C,那么B和C如何去找到公共的A呢?這里是通過了B和C的兩個指針,指向的一張表。這兩個指針叫 虛基表指針 ,這兩個表叫 虛基表 。虛基表中存的 偏移量 。通過偏移量可以找到下面的A。
到了這里,關(guān)于【C++】萬字一文全解【繼承】及其特性__[剖析底層化繁為簡](20)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!