做網(wǎng)站如何選擇數(shù)據(jù)源永久免費(fèi)進(jìn)銷存管理軟件手機(jī)版
目錄
一、前言
二、什么是繼承 ??
💢繼承的概念💢?
💢繼承的定義💢??
🥝定義格式?
🍇繼承權(quán)限?
三、基類與派生類對象的賦值轉(zhuǎn)換?
四、繼承的作用域?
五、派生類中的默認(rèn)成員函數(shù)?
💢默認(rèn)成員函數(shù)的調(diào)用?💢
🔥構(gòu)造函數(shù)與析構(gòu)函數(shù)🔥
🔥拷貝構(gòu)造🔥?
🔥賦值運(yùn)算符重載?🔥
💢顯示成員函數(shù)的調(diào)用?💢?
🔥構(gòu)造函數(shù)?🔥
🔥拷貝構(gòu)造?🔥
🔥賦值運(yùn)算符重載?🔥
🔥析構(gòu)函數(shù)🔥?
六、繼承與友元?
七、繼承與靜態(tài)成員
八、菱形繼承?
💧 單繼承💧
💧 多繼承💧
💧 菱形繼承💧?
🍍概念?
🍉現(xiàn)象?
九、繼承和組合?
十、繼承的總結(jié)和反思?
十一、共勉?
一、前言
? ?繼承
?是 面向?qū)ο笕筇匦灾?/span>(封裝、繼承、多態(tài)),所有的面向?qū)ο?#xff08;OO
)語言都具備這三個(gè)基本特征,封裝相關(guān)概念已經(jīng)在《類和對象》系列中介紹過了,今天主要學(xué)習(xí)的是?繼承
,即如何在父類的基礎(chǔ)之上,構(gòu)建出各種功能更加豐富的子
二、什么是繼承 ??
什么是繼承?是繼承 -- 遺產(chǎn) 還是繼承 -- 花唄?答案都不是,先來看看官方解釋:
繼承(inheritance)機(jī)制是 ----面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的重要的手段,它允許程序員在保持原有基類(父類)特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱為派生類(子類)?
💢繼承的概念💢?
繼承相關(guān)概念:?
- 被繼承對象:父類 / 基類 (
base
) - 繼承方:子類 / 派生類 (
derived
)
繼承的本質(zhì)?就是 ------------?復(fù)用代碼?
舉個(gè)例子 : 假設(shè)我現(xiàn)在要設(shè)計(jì)一個(gè)校園管理系統(tǒng),那么肯定會設(shè)計(jì)很多角色類,比如學(xué)生、老師、保安、保潔等等之類的。?
設(shè)計(jì)好以后,我們發(fā)現(xiàn),有些數(shù)據(jù)和方法是每個(gè)角色都有的,而有些則是每個(gè)角色獨(dú)有的。?
為了復(fù)用代碼、提高開發(fā)效率,可以從各種角色中選出共同點(diǎn),組成 基類,比如每個(gè) 人 都有姓名、年齡、聯(lián)系方式等基本信息,而 教職工 與 學(xué)生 的區(qū)別就在于 管理與被管理,因此可以在 基類 的基礎(chǔ)上加一些特殊信息如教職工號 表示 教職工,加上 學(xué)號 表示學(xué)生,其他細(xì)分角色設(shè)計(jì)也是如此
這樣就可以通過?繼承
?的方式,復(fù)用 基類 的代碼,劃分出各種 子類?
?像上面共同擁有的數(shù)據(jù)和方法我們可以重新設(shè)計(jì)一個(gè)類Person ,然后讓 Student 和 Teacher 去繼承它,如下:
// 大眾類 --- 基礎(chǔ)屬性
class Person
{
public:Person(string name = string(), string tell = string(), int age = int()):_name(name),_tell(tell),_age(age){}void Print(){cout << "我的名字是 :" << _name << endl;cout << "我的電話是 :" << _tell << endl;cout << "我的年齡是 :" << _age << endl;}
protected:string _name; // 姓名string _tell; // 電話int _age; // 年齡
};// 學(xué)生類 --- 派生/子屬性
class Student : public Person
{
public:Student(int stuId = 1578):Person("XAS","123456789",26),_stuId(stuId){cout << "我是一個(gè)學(xué)生" << endl;cout << "以下是我的個(gè)人信息 :" << endl;cout << endl;cout << "我的學(xué)號為 :" << _stuId << endl;}
protected:int _stuId; // 學(xué)號
};// 老師類 --- 派生/子屬性
class Teacher : public Person
{
public:Teacher(int workId = 2024916):Person("xas","987654321",26),_workId(workId){cout << "我是一個(gè)老師" << endl;cout << "以下是我的個(gè)人信息 :" << endl;cout << endl;cout << "我的工號為 :" << _workId << endl;}
protected:int _workId; // 工號
};int main()
{Student s;s.Print();cout << "---------------------" << endl;cout << "---------------------" << endl;Teacher t;t.Print();return 0;
}
繼承后,父類的 Person 的成員(成員函數(shù)+成員變量)都會變成子類的一部分。這里體現(xiàn)出了Student 和 Teacher 復(fù)用了 Person?
💢繼承的定義💢??
了解完 繼承相關(guān)概念 后,就可以開始學(xué)習(xí) 使用繼承?了?
🥝定義格式?
格式如下: Person 是?父類,也稱作基類; Student 是 子類,也稱作派生類。?
格式為 :子類 : 繼承方式 父類
,比如?class a : public b
?就表示?a
?繼承了?b
,并且還是?公有繼承?
注:Java
?中的繼承符號為?extern
,而?C++
?中為?:
?
🍇繼承權(quán)限?
繼承有權(quán)限的概念,分別為:公有繼承(public
)、保護(hù)繼承(protected
)、私有繼承(private
)?
沒錯(cuò),與?類?中的訪問?限定修飾符?一樣,不過這些符號在這里表示?繼承權(quán)限?
簡單回顧下各種限定符的用途
- 公有 public:公開的,任何人都可以訪問
- 保護(hù) protected:保護(hù)的,只有當(dāng)前類和子類可以訪問
- 私有 private:私有的,只允許當(dāng)前類進(jìn)行訪問
權(quán)限大小:公有 > 保護(hù) > 私有
保護(hù) protected 比較特殊,只有在 繼承 中才能體現(xiàn)它的價(jià)值,否則與 私有 作用一樣
此時(shí)我們發(fā)現(xiàn)? ---- 訪問權(quán)限:三種? ? ?繼承權(quán)限:三種
根據(jù)排列組合,可以列出以下多種搭配方案?
父類成員 / 繼承權(quán)限 | public | protected | private |
父類的?public ?成員 | 外部可見,子類中可見 | 外部不可見,子類中可見 | 外部不可見,子類中可見 |
父類的?protected ?成員 | 外部不可見,子類中可見 | 外部不可見,子類中可見 | 外部不可見,子類中可見 |
父類的?private ?成員 | 都不可見 | 都不可見 | 都不可見 |
注:所謂的--外部--其實(shí)就是 子類對象
總結(jié):
- 無論是哪種繼承方式,父類中的?
private
?成員始終不可被 [子類 / 外部] 訪問;當(dāng)外部試圖訪問父類成員時(shí),依據(jù)?min(父類成員權(quán)限, 子類繼承權(quán)限)
,只有最終權(quán)限為?public
?時(shí),外部才能訪問 - 在實(shí)際運(yùn)用中一般使用都是 public 繼承,幾乎很少使用 protetced/private 繼承,也不提倡使用?protetced/private繼承,因?yàn)?protetced/private 繼承下來的成員都只能在派生類的類里面使用,實(shí)際中擴(kuò)展維護(hù)性不強(qiáng)。
如何證明呢??通過一下的代碼,我們來驗(yàn)證上面的結(jié)論!!
// 父類
class A
{
public:int _a;
protected:int _b;
private:int _c;
};// 子類
class B : public A
{
public:B(){cout << _a << endl;cout << _b << endl;cout << _c << endl;}
};int main()
{// 外部(子類對象)B b; b._a;
}
- public 繼承?
- ?protected?繼承?
- ??private?繼承?
之所以說?
C++
?的繼承機(jī)制設(shè)計(jì)復(fù)雜了,是因?yàn)?protected
?和?private
?繼承時(shí)的效果一樣
其實(shí)?C++
?中搞這么多種情況(9種)完全沒必要,實(shí)際使用中,最常見到的組合為?public : public
?和?protected : public
?
如何優(yōu)雅的使用好? ---- 繼承權(quán)限 ??
對于只想自己類中查看的成員,設(shè)為?private
,對于想共享給子類使用的成員,設(shè)為?protected
,其他成員都可以設(shè)為?public
比如在張三家中,張三家的房子面積允許公開,家庭存款只限家庭成員共享,而個(gè)人隱私數(shù)據(jù)則可以設(shè)為私有
class Home
{
public:int area = 500; //500 平米的大房子
};class Father : public Home
{
protected:int money = 50000; //存款五萬
private:int privateMoney = 100; //私房錢,怎能公開?
};class Zhangsan : public Father
{
public:Zhangsan(){cout << "我是張三" << endl;cout << "我知道我家房子有 " << area << " 平方米" << endl;cout << "我也知道我家存款有 " << money << endl;cout << "但我不知道我爸爸的私房錢有多少" << endl;}
};class Xiaoming
{
public:Xiaoming(){cout << "我是小明" << endl;cout << "我只知道張三家房子有 " << Home().area << " 平方米" << endl;cout << "其他情況我一概不知" << endl;}
};int main()
{Zhangsan z;cout << "================" << endl;Xiaoming x;return 0;
}
三、基類與派生類對象的賦值轉(zhuǎn)換?
在繼承中,允許將 子類 對象直接賦值給 父類,但不允許 父類 對象賦值給 子類?
- 這其實(shí)很好理解,兒子以后可以當(dāng)父親,父親還可以當(dāng)兒子嗎?
并且這種?賦值?是非常自然的,編譯器直接處理,不需要調(diào)用?賦值重載?等函數(shù)?
派生類對象? ?可以賦值給? ? ? 基類的對象 / 基類的指針 / 基類的引用。?
(1) 子類對象可以賦值給父類對象?
//基類
class Person
{
public:string _name; // 姓名string _sex; // 性別int _age; // 年齡
};//派生類
class Student : public Person
{
public:int _id;
};int main()
{Person p;Student s;s._name = "張三";s._sex = "男";s._age = 20;s._id = 8888;p = s; // 子類對象賦值給父類對象return 0;
}
?通過調(diào)式可以看到,為什么沒有把 id 賦值過去呢?
這里有個(gè)形象的說法叫切片或者切割,相當(dāng)于把派生類中父類那部分切來賦值過去,如圖所示:?
(2) 子類對象可以賦值給父類指針??
//基類
class Person
{
public:string _name; // 姓名string _sex; // 性別int _age; // 年齡
};//派生類
class Student : public Person
{
public:int _id;
};int main()
{Student s;s._name = "張三";s._sex = "男";s._age = 20;s._id = 8888;Person* p = &s;return 0;
}
可以看到,當(dāng)父類對象是一個(gè)指針的時(shí)候,照樣可以賦值過去:
子類對象賦值給父類指針切片圖:?
(3) 子類對象可以賦值給父類引用?
//基類
class Person
{
public:string _name; // 姓名string _sex; // 性別int _age; // 年齡
};//派生類
class Student : public Person
{
public:int _id;
};int main()
{Student s;s._name = "張三";s._sex = "男";s._age = 20;s._id = 8888;Person& rp = s;return 0;
}
?可以看到,當(dāng)父類對象是一個(gè)引用的時(shí)候,也可以賦值過去:
?子類對象賦值給父類引用切換圖片:
(4) 父類對象不能賦值給子類對象
//基類
class Person
{
public:string _name; // 姓名string _sex; // 性別int _age; // 年齡
};//派生類
class Student : public Person
{
public:int _id;
};int main()
{Student s;Person p;s = p;return 0;
}
編譯會報(bào)錯(cuò):?
四、繼承的作用域?
在繼承體系中 基類 和 派生類 都有獨(dú)立的作用域,如果子類和父類中有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫?隱藏,也叫重定義。
代碼示例: Student 的 _num 和 Person的 _num 構(gòu)成隱藏關(guān)系,可以看出這樣代碼雖然能跑,但是非常容易混淆。
// 基類
class Person
{
protected:string _name = "Edison"; // 姓名int _num = 555; // 身份證號
};// 派生類
class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << "學(xué)號:" << _num << endl;}
protected:int _num = 888; // 學(xué)號
};int main()
{Student s1;s1.Print();return 0;
}
?運(yùn)行可以看到,訪問的是子類中的_num?(類似于局部優(yōu)先的原則)
那么如果我想訪問父類中的_num 呢 ? 可以使用基類 :: 基類成員顯示的去訪問 :?
// 基類
class Person
{
protected:string _name = "Edison"; // 姓名int _num = 555; // 身份證號
};// 派生類
class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << " 身份證號:" << Person::_num << endl;}
protected:int _num = 888; // 學(xué)號
};int main()
{Student s1;s1.Print();return 0;
}
?可以看到,此時(shí)就是訪問的父類中的 _num?
還有一點(diǎn)需要注意的是 : 如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。?
// 基類
class A
{
public:void fun(){cout << "A::func()" << endl;}
};// 派生類
class B : public A
{
public:void fun(int i){cout << "B::func()" << endl;cout << "func(int i)->" << i << endl;}
};int main()
{B b;b.fun(10);return 0;
}
?可以看到,默認(rèn)是去調(diào)用子類的 fun() 函數(shù),因?yàn)槌蓡T函數(shù)滿足函數(shù)名相同就構(gòu)成隱藏。
如果想調(diào)用父類的 fun() 還是需要指定作用域?
// 基類
class A
{
public:void fun(){cout << "A::func()" << endl;}
};// 派生類
class B : public A
{
public:void fun(int i){cout << "B::func()" << endl;cout << "func(int i)->" << i << endl;}
};int main()
{B b;b.A::fun();return 0;
}
?運(yùn)行可以看到,此時(shí)就是調(diào)用父類中的 fun()
注意 :B 中的 fun 和 A 中的 fun 不是構(gòu)成函數(shù)重載,而是隱藏 !函數(shù)重載的要求是在同一作用域里面!!
另外,在實(shí)際中在繼承體系里面最好不要定義同名的成員。
五、派生類中的默認(rèn)成員函數(shù)?
派生類(子類)也是?類,同樣會生成?六個(gè)默認(rèn)成員函數(shù)(用戶未定義的情況下)
不同于單一的?類,子類?是在?父類?的基礎(chǔ)之上創(chuàng)建的,因此它在進(jìn)行相關(guān)操作時(shí),需要為?父類?進(jìn)行考慮
這里我們只以下面的兩個(gè)類為基礎(chǔ),討論四類默認(rèn)成員函數(shù):構(gòu)造函數(shù)、拷貝構(gòu)造、賦值運(yùn)算符重載、析構(gòu)函數(shù)?
class Person
{
public:Person(const std::string name = std::string(), const int age = 18, const int sex = 1): _name(name), _age(age), _sex(sex){std::cout << "Person()" << std::endl;}Person(const Person& p): _name(p._name), _age(p._age), _sex(p._sex){std::cout << "Person(const Person& p)" << std::endl;}Person& operator= (const Person& p){std::cout << "operator= (const Person& p)" << std::endl;if (this != &p){_name = p._name;}return *this;}~Person(){std::cout << "~Person()" << std::endl;}
protected:std::string _name;int _age = 18;int _sex = 1;
};class Student : public Person
{
public:
protected:long long _st_id;
};
💢默認(rèn)成員函數(shù)的調(diào)用?💢
🔥構(gòu)造函數(shù)與析構(gòu)函數(shù)🔥
int main()
{Student st;return 0;
}
output:?
Person() // s1 的構(gòu)造
~Person() // s1 的析構(gòu)
說明了,派生類的默認(rèn)構(gòu)造函數(shù)和默認(rèn)析構(gòu)函數(shù)都會 -----自動(dòng)調(diào)用基類的構(gòu)造和析構(gòu)?
🔥拷貝構(gòu)造🔥?
int main()
{Student st1;Student st2(st1);return 0;
}
?output:
Person() // s1 的構(gòu)造函數(shù)
Person(const Person& p) // s2 拷貝構(gòu)造
~Person() // s2 的析構(gòu)函數(shù)
~Person() // s1 的析構(gòu)函數(shù)
說明了,派生類的默認(rèn)拷貝構(gòu)造會自動(dòng)調(diào)用基類的拷貝構(gòu)造?
🔥賦值運(yùn)算符重載?🔥
int main()
{Student st1, st2;st1 = st2;return 0;
}
output:
Person() // s1 的拷貝構(gòu)造
Person() // s2 的拷貝構(gòu)造
operator= (const Person& p) // 賦值重載
~Person() // s2 的析構(gòu)函數(shù)
~Person() // s1 的析構(gòu)函數(shù)
說明了,派生類的默認(rèn)賦值運(yùn)算符重載會自動(dòng)調(diào)用基類的賦值運(yùn)算符重載?
實(shí)際上,我們可以將派生類的成員分成三部分:基類成員、內(nèi)置類型成員、自定義類型成員?
-
繼承相較于我們以前學(xué)的類和對象,可以說就是多了基類那一部分
-
當(dāng)調(diào)用派生類的默認(rèn)成員函數(shù)時(shí),對于基類成員都會調(diào)用對應(yīng)基類的默認(rèn)成員函數(shù)來處理
💢顯示成員函數(shù)的調(diào)用?💢?
🔥構(gòu)造函數(shù)?🔥
當(dāng)實(shí)現(xiàn)派生類的構(gòu)造函數(shù)時(shí),就算不顯示調(diào)用基類的構(gòu)造,系統(tǒng)也會自動(dòng)調(diào)用基類的構(gòu)造:?
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id){std::cout << "Student()" << std::endl;}
protected:long long _st_id;
};
int main()
{Student st;return 0;
}
?output:
Person()
Student()
~Person()
如果需要顯示的調(diào)用基類的構(gòu)造函數(shù),應(yīng)該這樣寫:?
Student(long long st_id = 111): _st_id(st_id), Person("xas", 18)
{std::cout << "Student()" << std::endl;
}
?特別注意:
Person("xas", 18)
如果放在初始化列表,那就使顯式調(diào)用基類的構(gòu)造函數(shù);如果放在函數(shù)體內(nèi),那就使創(chuàng)建一個(gè)基類的匿名對象?
🔥拷貝構(gòu)造?🔥
?當(dāng)實(shí)現(xiàn)派生類的拷貝構(gòu)造時(shí),如果沒有顯式調(diào)用基類的拷貝構(gòu)造,那么系統(tǒng)就會自動(dòng)調(diào)用基類的構(gòu)造
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}Student(const Student& s): _st_id(s._st_id){std::cout << "Student(const Student& s)" << std::endl;}
protected:long long _st_id;
};
int main()
{Student st1;Student st2(st1);return 0;
}
output:?
Person()
Student()
Person() //系統(tǒng)自動(dòng)調(diào)用了基類的構(gòu)造
Student(const Student& s)
~Person()
~Person()
?也可以像顯示調(diào)用構(gòu)造函數(shù)一樣顯式調(diào)用基類的拷貝構(gòu)造:
Student(const Student& s): Person(s), _st_id(s._st_id)
{std::cout << "Student(const Student& s)" << std::endl;
}
🔥賦值運(yùn)算符重載?🔥
?在實(shí)現(xiàn)派生類的賦值運(yùn)算符重載時(shí),如果沒有顯式調(diào)用基類的賦值運(yùn)算符重載,系統(tǒng)也不會自動(dòng)調(diào)用基類的賦值運(yùn)算符重載
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}Student& operator= (const Student& s){std::cout << "operator= (const Student& s)" << std::endl;if (this != &s){_st_id = s._st_id;}return *this;}
protected:long long _st_id;
};
int main()
{Student st1, st2;st1 = st2;return 0;
}
output:?
Person()
Student()
Person()
Student()
operator= (const Student& s)
~Person()
~Person()
因此,在實(shí)現(xiàn)派生類的賦值運(yùn)算符重載時(shí),必須顯示調(diào)用基類的賦值運(yùn)算符重載:?
Student& operator= (const Student& s)
{std::cout << "operator= (const Student& s)" << std::endl;if (this != &s){Person::operator=(s); //由于基類和派生類的賦值運(yùn)算符重載構(gòu)成隱藏,因此要用 :: 指定類域_st_id = s._st_id;}return *this;
}
🔥析構(gòu)函數(shù)🔥?
?在實(shí)現(xiàn)派生類的析構(gòu)函數(shù)時(shí),不要顯式調(diào)用基類的析構(gòu)函數(shù),系統(tǒng)會在派生類的析構(gòu)完成后自動(dòng)調(diào)用基類的析構(gòu)
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}~Student(){std::cout << "~Student()" << std::endl;}
protected:long long _st_id;
};int main()
{Student st1;return 0;
}
output:?
Person()
Student()
~Student()
~Person()
- 和前面的默認(rèn)成員函數(shù)不同,在實(shí)現(xiàn)派生類的析構(gòu)時(shí),基類的析構(gòu)不能顯式調(diào)用
- 這是因?yàn)?#xff0c;如果顯示調(diào)用了基類的析構(gòu),就會導(dǎo)致基類成員的資源先被清理,如果此時(shí)派生類成員還訪問了基類成員指向的資源就,就會導(dǎo)致野指針問題
- 因此,必須保證析構(gòu)順序?yàn)?strong>先子后父,保證數(shù)據(jù)訪問的安全
六、繼承與友元?
友元關(guān)系不能繼承,也就是說 基類友元不能訪問子類私有和保護(hù)成員,只能訪問自己的私有和保護(hù)成員。?
下面代碼中,Display 函數(shù)是基類 Person 的友元,但是 Display 函數(shù)不是派生類 Student 的友元,也就是說 Display 函數(shù)無法訪
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; // 無法訪問
}int main()
{Person p;Student s;Display(p, s);return 0;
}
可以看到運(yùn)行會報(bào)錯(cuò):
如果想讓 Display 函數(shù)也能夠訪問派生類Student 的私有和保護(hù)成員,只需要在派生類Student 當(dāng)中進(jìn)行友元聲明。?
class Student;class Person
{
public:friend void Display(const Person& p, const Student& s); // 聲明Display是Person的友元
protected:string _name; // 姓名
};class Student : public Person
{
public:friend void Display(const Person& p, const Student& s); // 聲明Display是Student的友元
protected:int _stuNum; // 學(xué)號
};void Display(const Person& p, const Student& s)
{cout << p._name << endl; // 可以訪問cout << s._stuNum << endl; // 可以訪問
}int main()
{Person p;Student s;Display(p, s);return 0;
}
七、繼承與靜態(tài)成員
?如果基類中定義了static 靜態(tài)成員,則整個(gè)繼承體系里面只有一個(gè)這樣的成員。 無論派生出多少個(gè)子類,都只有一個(gè) static 成員實(shí)例。
下面代碼中,在基類 Person 當(dāng)中定義了靜態(tài)成員變量 _ count, 派生類 Student 和 Graduate 繼承了Person, 但是,在整個(gè)繼承體?
// 基類
class Person
{
public:Person() { ++_count; }
protected:string _name; // 姓名
public:static int _count; // 統(tǒng)計(jì)人的個(gè)數(shù)。
};// 靜態(tài)成員在類外面定義
int Person::_count = 0; // 派生類
class Student : public Person
{
protected:int _stuNum; // 學(xué)號
};// 派生類
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};int main()
{Student s1;Student s2;Student s3;Graduate s4;Person s;cout << " 人數(shù) :" << Person::_count << endl;cout << " 人數(shù) :" << Student::_count << endl;cout << " 人數(shù) :" << s4._count << endl;return 0;
}
?我們定義了5個(gè)對象,那么每定義一個(gè)對象都會去調(diào)用一次++ _count ,打印以后可以看到,這幾個(gè)對象里面的 _count 都是一樣的:
同時(shí),我們還可以打印一下地址,可以看到也是同-個(gè):?
?總結(jié):?關(guān)于父類中的靜態(tài)成員,子類繼續(xù)下來以后都是同一個(gè),類似于“傳家寶"。
八、菱形繼承?
💧 單繼承💧
一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承???
💧 多繼承💧
?一個(gè)子類有兩個(gè)或以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承
💧 菱形繼承💧?
?
C++
?支持多繼承,即支持一個(gè)子類繼承多個(gè)父類,使其基礎(chǔ)信息更為豐富,但凡事都有雙面性,多繼承 在帶來巨大便捷性的同時(shí),也帶來了個(gè)巨大的坑:菱形繼承問題
🍍概念?
首先?
C++
?允許出現(xiàn)多繼承的情況,如下圖所示?
這樣看很正常是吧,但如果出現(xiàn)以下這種?重復(fù)繼承?的情況,就比較麻煩了?
此時(shí)?普通人X?會糾結(jié)于使用哪一個(gè)?不用吃飯?的屬性!這對于編譯器來說,是一件無法處理的事?
🍉現(xiàn)象?
將上述概念轉(zhuǎn)化為代碼,觀察實(shí)際現(xiàn)象
注:多繼承時(shí),只需要在 父類 之后,添加?
,
?號,繼續(xù)增加想要繼承的父類
class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : public Person
{};//研究生
class Postgraduate : public Person
{};//畢業(yè)生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name = "zhangsan";return 0;
}
無法編譯!?
原因分析:?
?Undergraduate?
中繼承了?Person
?的?_name
,Postgraduate?
也繼承了?Person
?的?_name
Graduate?
多繼承?Undergraduate?
、Postgraduate?
后,同時(shí)擁有了兩個(gè)?_name
,使用時(shí),無法區(qū)分!
通過監(jiān)視窗口查看信息:?
解決方法:?
想要解決二義性很簡單,通過?::
?限制訪問域即可?
Graduate g1;
g1.Undergraduate::_name = "zhangsan";
cout << g1.Undergraduate::_name << endl;
但這沒有從本質(zhì)上解決問題!而且還沒有解決數(shù)據(jù)冗余問題?
?真正的解決方法:虛繼承
?注:虛繼承是專門用來解決 菱形繼承 問題的,與多態(tài)中的虛函數(shù)沒有直接關(guān)系
虛繼承:在菱形繼承的腰部繼承父類時(shí),加上?
virtual
?關(guān)鍵字修飾被繼承的父類?
class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : virtual public Person
{};//研究生
class Postgraduate : virtual public Person
{};//畢業(yè)生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name = "zhangsan";cout << g1._name << endl;return 0;
}
此時(shí)可以解決?菱形繼承?的?數(shù)據(jù)冗余?和?二義性?問題?
?虛繼承是如何解決菱形繼承問題的?
- 利用?虛基表?將冗余的數(shù)據(jù)存儲起來,此時(shí)冗余的數(shù)據(jù)合并為一份
- 原來存儲 冗余數(shù)據(jù) 的位置,現(xiàn)在用來存儲?虛基表指針
此時(shí)無論這個(gè) 冗余 的數(shù)據(jù)存儲在何處,都能通過 基地址 + 偏移量 的方式進(jìn)行訪問?
九、繼承和組合?
public 繼承是一種 is-a 的關(guān)系。也就是說每個(gè)派生類對象都是一個(gè)基類對象。?
而 組合 是一種 has-a 的關(guān)系。假設(shè) B 組合了 A,每個(gè) B對象中都有一個(gè) A對象。
舉個(gè)例子: 轎車和奔馳就構(gòu)成 is-a 的關(guān)系,所以可以使用繼承。?
// 車類
class Car
{
protected:string _colour = "黑色"; // 顏色string _num = "川A66688"; // 車牌號
};// 奔馳
class Benz : public Car
{
public:void Drive(){cout << "好開-操控" << endl;}
};
再舉個(gè)例子:汽車和輪胎之間就是 has-a 的關(guān)系,它們之間則適合使用組合。?
// 輪胎
class Tire {
protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺寸};// 汽車
class Car {
protected:string _colour = "黑色"; // 顏色string _num = "川A66688"; // 車牌號Tire _t; // 輪胎
};
?實(shí)際項(xiàng)目中,更推薦使用?組合?的方式,這樣可以做到?解耦,避免因父類的改動(dòng)而直接影響到子類
- 公有繼承:
is-a
?—> 高耦合,可以直接使用父類成員- 組合:
has-a
?—> 低耦合,可以間接使用父類成員
十、繼承的總結(jié)和反思?
?1. 很多人說C++語法復(fù)雜,其實(shí)多繼承就是一個(gè)體現(xiàn)。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實(shí)現(xiàn)就很復(fù)雜。所以一般不建議設(shè)計(jì)出多繼承,一定不要設(shè)計(jì)出菱形繼承。否則在復(fù)雜度及性能上都有問題。
2. 多繼承可以認(rèn)為是C++的缺陷之一,很多后來的面向?qū)ο笳Z言都沒有多繼承,如Java。
十一、共勉?
?以下就是我對 【C++】繼承 的理解,如果有不懂和發(fā)現(xiàn)問題的小伙伴,請?jiān)谠u論區(qū)說出來哦,同時(shí)我還會繼續(xù)更新對 【C++】多態(tài)?的理解,請持續(xù)關(guān)注我哦!!!????