C++繼承模式詳解
繼承
繼承的概念
繼承機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類(lèi)特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。
繼承的定義

上面的基類(lèi)也可以叫父類(lèi),派生類(lèi)也可以叫子類(lèi)。
繼承關(guān)系和訪(fǎng)限定符

繼承方式

接下來(lái)用代碼測(cè)試上面的繼承方式
class Person
{
public :
void Print ()
{
cout<<_name <<endl;
}
protected :
string _name = "張三" ; // 姓名
private :
int _age = 18 ; // 年齡
};
class Student : public Person
{
protected :
int _stunum = 22; // 學(xué)號(hào)
};
public繼承
上面是給的缺省值來(lái)測(cè)試沒(méi)寫(xiě)構(gòu)造函數(shù)
s就繼承了Person的name,age,基類(lèi)中private的age在物理上繼承了但在語(yǔ)法上但是不能訪(fǎng)問(wèn)的。
也可以調(diào)用基類(lèi)的成員函數(shù),但是不能直接訪(fǎng)問(wèn)基類(lèi)中private的成員,prootected可以在派生類(lèi)中訪(fǎng)問(wèn),不能再在類(lèi)外訪(fǎng)問(wèn)
protected繼承
protected繼承,在類(lèi)外連基類(lèi)的public成員函數(shù)都不能用了,只能在派生類(lèi)的類(lèi)里面使用。
同樣基類(lèi)中私有的不能訪(fǎng)問(wèn)
private繼承就都是私有的了。
總結(jié):
- 1.基類(lèi)private成員在派生類(lèi)中無(wú)論以什么方式繼承都是不可見(jiàn)的。這里的不可見(jiàn)是指基類(lèi)的私有成員還是被繼承到了派生類(lèi)對(duì)象中,但是語(yǔ)法上限制派生類(lèi)對(duì)象不管在類(lèi)里面還是類(lèi)外面都不能去訪(fǎng)問(wèn)它。
- 2.基類(lèi)private成員在派生類(lèi)中是不能被訪(fǎng)問(wèn),如果基類(lèi)成員不想在類(lèi)外直接被訪(fǎng)問(wèn),但需要在派生類(lèi)中能訪(fǎng)問(wèn),就定義為protected。可以看出保護(hù)成員限定符是因繼承才出現(xiàn)的.
- 3.基類(lèi)的私有成員在子類(lèi)中都是不可見(jiàn)的,其他成員在子類(lèi)中等于權(quán)限最小的那個(gè)
- 4.class的默認(rèn)繼承方式是private,struct默認(rèn)的繼承方式是public,最好顯示的寫(xiě)出繼承方式
- 5.在實(shí)際應(yīng)用一般使用public繼承,很少使用protected和private。
父類(lèi)和子類(lèi)對(duì)象賦值轉(zhuǎn)化
class Person
{
protected:
string _name; // 姓名
string _sex; // 性別
int _age; // 年齡
};
class Student : public Person
{
public:
int _No; // 學(xué)號(hào)
};

子類(lèi)可以給父類(lèi),父類(lèi)不能給子類(lèi),不僅可以是子類(lèi)的對(duì)象,也可以是指針和引用
Student s; Person p; p = s; Person *ptr = &s;//子類(lèi)賦給父類(lèi)指針 Person &ref = s;//子類(lèi)賦給父類(lèi)引用



派生類(lèi)對(duì)象 可以賦值給 基類(lèi)的對(duì)象 / 基類(lèi)的指針 / 基類(lèi)的引用。這里有個(gè)形象的說(shuō)法叫切片或者切割。寓意把派生類(lèi)中父類(lèi)那部分切來(lái)賦值過(guò)去。
基類(lèi)對(duì)象不能賦值給派生類(lèi)對(duì)象
基類(lèi)的指針可以通過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換賦值給派生類(lèi)的指針。但是必須是基類(lèi)的指針是指向派生類(lèi)對(duì)象時(shí)才是安全的。等到子類(lèi)中的默認(rèn)函數(shù)就會(huì)用到切片
繼承中的作用域
class Person
{
protected:
string _name = "法外狂徒"; // 姓名
int _num = 11; // 身份證號(hào)
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 身份證號(hào):" << _num << endl;
cout << " 學(xué)號(hào):" << _num << endl;
}
protected:
int _num = 2; // 學(xué)號(hào)
};

還有成員函數(shù)的隱藏
class A {
public:
void fun(double x)
{
cout << "fun()->x"<< x << endl;
}
};
class B : public A {
public:
void fun(int i)
{
cout << "fun()->" << i << endl;
}
};
int main()
{
B b;
b.fun(10);
b.A::fun(11.1);//加作用域
return 0;
}
父類(lèi)和子類(lèi)函數(shù)名相同不是重載而是隱藏,函數(shù)重載是在同一作用域,不同的作用域是隱藏
在子類(lèi)成員函數(shù)中,可以使用 基類(lèi)::基類(lèi)成員 顯示訪(fǎng)問(wèn)
在寫(xiě)代碼中最好不要定義同名的成員
子類(lèi)的默認(rèn)成員函數(shù)
在類(lèi)和對(duì)象的時(shí)候講了6個(gè)默認(rèn)的成員函數(shù),現(xiàn)在子類(lèi)中講4個(gè),構(gòu)造,拷貝構(gòu)造,賦值和析構(gòu)

class Person //父類(lèi)
{
public:
Person(const char* name = "李四")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
//子類(lèi)
class Student : public Person
{
public:
//構(gòu)造函數(shù)
Student(const char* name, int num)
: Person(name)//調(diào)用父類(lèi)的構(gòu)造函數(shù)初始化父類(lèi)的成員
, _num(num)//初始化子類(lèi)的成員
{
cout << "Student()" << endl;
}
//拷貝構(gòu)造
Student(const Student& s)
: Person(s)//這里就用到了切片,切父類(lèi)的成員類(lèi)拷貝
, _num(s._num)//拷貝子類(lèi)的
{
cout << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);//調(diào)用父類(lèi)的賦值
_num = s._num;//賦值子類(lèi)自己的
}
return *this;
}
~Student()
{
//子類(lèi)的析構(gòu)函數(shù)完成清理后會(huì)自動(dòng)調(diào)用父類(lèi)的析構(gòu)函數(shù)
cout << "~Student()" << endl;
}
protected:
int _num; //學(xué)號(hào)
};
總結(jié):
- 派生類(lèi)的構(gòu)造函數(shù)必須調(diào)用基類(lèi)的構(gòu)造函數(shù)初始化基類(lèi)的那一部分成員。如果基類(lèi)沒(méi)有默認(rèn)的構(gòu)造函數(shù),則必須在派生類(lèi)構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
- 派生類(lèi)的拷貝構(gòu)造函數(shù)必須調(diào)用基類(lèi)的拷貝構(gòu)造完成基類(lèi)的拷貝初始化。
- 派生類(lèi)的operator=必須要調(diào)用基類(lèi)的operator=完成基類(lèi)的復(fù)制。
- 派生類(lèi)的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類(lèi)的析構(gòu)函數(shù)清理基類(lèi)成員。因?yàn)檫@樣才能保證派生類(lèi)對(duì)象先清理派生類(lèi)成員再清理基類(lèi)成員的順序。
- 派生類(lèi)對(duì)象初始化先調(diào)用基類(lèi)構(gòu)造再調(diào)派生類(lèi)構(gòu)造。
- 派生類(lèi)對(duì)象析構(gòu)清理先調(diào)用派生類(lèi)析構(gòu)再調(diào)基類(lèi)的析構(gòu)。
繼承與友元
友元關(guān)系不能繼承,父類(lèi)友元不能訪(fǎng)問(wèn)子類(lèi)私有和保護(hù)成員
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum; // 學(xué)號(hào)
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;//可以訪(fǎng)問(wèn)
cout << s._stuNum << endl;//要在子類(lèi)中加上友元才能訪(fǎng)問(wèn),不加會(huì)報(bào)錯(cuò)
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
繼承與靜態(tài)成員
基類(lèi)定義了static靜態(tài)成員,則整個(gè)繼承體系里面只有一個(gè)這樣的成員。無(wú)論派生出多少個(gè)子類(lèi),都只有一個(gè)static成員實(shí)例 。
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 統(tǒng)計(jì)人的個(gè)數(shù)。
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _id; // 學(xué)號(hào)
};
class Graduate : public Student
{
protected:
string _Course; // 科目
};
int main()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << " 人數(shù) :" << Person::_count << endl;
cout << " 人數(shù) :" << Student::_count << endl;
cout << " 人數(shù) :" << &Person::_count << endl;
cout << " 人數(shù) :" << &Student::_count << endl;
return 0;
}

再加上count的地址可以看出是同一個(gè)的count。計(jì)算出子類(lèi)實(shí)例化了多少個(gè)對(duì)象就可以在父類(lèi)中定義個(gè)count自加。
復(fù)雜的菱形繼承
單繼承:一個(gè)子類(lèi)只有一個(gè)直接父類(lèi)時(shí)稱(chēng)這個(gè)繼承關(guān)系為單繼承

多繼承:一個(gè)子類(lèi)有兩個(gè)或以上直接父類(lèi)時(shí)稱(chēng)這個(gè)繼承關(guān)系為多繼承

菱形繼承:菱形繼承是多繼承的一種特殊情況。

菱形繼承的問(wèn)題:從下面的對(duì)象成員模型構(gòu)造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問(wèn)題。在Assistant的對(duì)象中Person成員會(huì)有兩份。
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //學(xué)號(hào)
};
class Teacher : public Person
{
protected:
int _id; // 編號(hào)
};
class Assistant : public Student, public Teacher
{
protected:
string _Course; // 課程
};
int main()
{
// 這樣會(huì)有二義性無(wú)法明確知道訪(fǎng)問(wèn)的是哪一個(gè)
Assistant a;
//a._name = "peter";
// 顯示的調(diào)用解決了二義性,但數(shù)據(jù)冗余了
a.Student::_name = "蓋倫";
a.Teacher::_name = "亞索";
return 0;
}

虛繼承
虛擬繼承可以解決菱形繼承的二義性和數(shù)據(jù)冗余的問(wèn)題。如上面的繼承關(guān)系,在Student和Teacher的繼承Person時(shí)使用虛擬繼承,即可解決問(wèn)題。需要注意的是,虛擬繼承不要在其他地方去使用。

在菱形的腰部加上virtual關(guān)鍵字可以解決冗余。那虛繼承是怎么解決的呢?先來(lái)看看不用虛繼承的
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;
return 0;
}
我們可以通過(guò)內(nèi)存窗口來(lái)觀(guān)察對(duì)象成員的模型

菱形繼承帶來(lái)了二義性和數(shù)據(jù)冗余。
再來(lái)看看虛繼承的

虛繼承就解決了數(shù)據(jù)冗余和二義性,B和C中多了地址,在用內(nèi)存窗口看看這里的地址
2個(gè)指針叫虛基表指針指向虛基表,可以通過(guò)偏移量找到公共虛基類(lèi),此時(shí)A是在下面那為什么要找呢?
D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; B b = d;//把d賦給b,把d切過(guò)去那此時(shí)要怎么找到A呢?,所以就要用虛基表找
B類(lèi)中各個(gè)成員在內(nèi)存中的分布:

通過(guò)偏移量找到虛基類(lèi)。
還是不要用菱形繼承出現(xiàn)問(wèn)題,虛繼承使得對(duì)象模型很復(fù)雜,并且會(huì)有效率的影響。
繼承的總結(jié)
- C++語(yǔ)法復(fù)雜,其實(shí)多繼承就是一個(gè)體現(xiàn)。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實(shí)現(xiàn)就很復(fù)雜。所以一般不建議設(shè)計(jì)出多繼承,一定不要設(shè)計(jì)出菱形繼承。否則在復(fù)雜度及性能上都有問(wèn)題。
- 多繼承可以認(rèn)為是C++的缺陷之一.
組合
繼承是建立了父類(lèi)與子類(lèi)的關(guān)系,是一種“是”的關(guān)系,例如白貓是貓,組合是“有”的關(guān)系實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過(guò)繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類(lèi)之間的關(guān)系可以用繼承,可以用組合就用組合。
面試題
- 什么是菱形繼承?菱形繼承的問(wèn)題是什么?
菱形繼承是多繼承的一種特殊繼承,兩個(gè)子類(lèi)繼承同一個(gè)父類(lèi),而又有子類(lèi)同時(shí)繼承這兩個(gè)子類(lèi)。可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問(wèn)題。- 什么是菱形虛擬繼承?如何解決數(shù)據(jù)冗余和二義性的
在菱形繼承的腰部加上virtual,通過(guò)虛基表指針和虛基表中的偏移量可以找到虛基類(lèi),只存1份- 繼承和組合的區(qū)別?什么時(shí)候用繼承?什么時(shí)候用組合?
繼承是一種"是",組合是"有"的關(guān)系,父類(lèi)和子類(lèi)是的關(guān)系用繼承,是有的關(guān)系用組合。
以上就是C++繼承,由于作者水平有限,如有問(wèn)題還請(qǐng)指出!
到此這篇關(guān)于C++繼承模式詳解的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C++數(shù)據(jù)結(jié)構(gòu)之實(shí)現(xiàn)循環(huán)順序隊(duì)列
這篇文章主要介紹了 C++數(shù)據(jù)結(jié)構(gòu)之實(shí)現(xiàn)循環(huán)順序隊(duì)列的相關(guān)資料,需要的朋友可以參考下2017-01-01
c++連接mysql5.6的出錯(cuò)問(wèn)題總結(jié)
下面小編就為大家?guī)?lái)一篇c++連接mysql5.6的出錯(cuò)問(wèn)題總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,祝大家游戲愉快哦2016-12-12
C語(yǔ)言對(duì)組文件處理的相關(guān)函數(shù)小結(jié)
這篇文章主要介紹了C語(yǔ)言對(duì)組文件處理的相關(guān)函數(shù)小結(jié),包括setgrent()函數(shù)和getgrent()函數(shù)以及endgrent()函數(shù),需要的朋友可以參考下2015-08-08
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易的三子棋小游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易的三子棋小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
C++中volatile限定符的實(shí)現(xiàn)示例
volatile關(guān)鍵字在C和C++中用于確保編譯器不優(yōu)化特定變量的訪(fǎng)問(wèn),主要用于多線(xiàn)程和硬件交互場(chǎng)景,本文就來(lái)介紹C++中volatile限定符的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-11-11
C語(yǔ)言當(dāng)函數(shù)執(zhí)行成功時(shí)return1還是0
本文主要介紹了C語(yǔ)言當(dāng)函數(shù)執(zhí)行成功時(shí)return1還是0,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09







