繼承
繼承的基本概念和語法
- 子類繼承基類,基類派生子類,子類IsA基類。
- class 子類名 : 繼承方式描述符1 基類名1, 繼承方式描述符2 基類名2, ...
{
};
class Staff
{
}
class SoftwareEngineer:public Staff
{
}
class ProductManager:public Staff
{
}
- 繼承方式:公有繼承(public)、保護(hù)繼承(protected)、私有繼承(private)。
范例:Staff.cpp
公有繼承
-
一個(gè)子類類型的對(duì)象在任何時(shí)候都可以作為一個(gè)基類類型的對(duì)象,而不必使用顯示的類型轉(zhuǎn)換,前提是兩者(子類及其基類)都是通過指針或引用操作的。
示意圖
范例:Staff02.cpp 我們?cè)谧宇愔锌梢灾苯邮褂没惖乃泄泻捅Wo(hù)成員,就象它們是在子類中聲明的那樣,但基類的私有成員在子類中雖然存在卻不可見,故無法直接使用。
class A
{
string name;
};
//
class B
{
int age
public:
B()
{
#error 子類不能訪問父類中的私有成員。
this->name = "wxx";
}
};
- 盡管基類的公有和保護(hù)成員在子類中直接可見,但我們也可以在子類中重新定義這些名字。由此產(chǎn)生的名字沖突可以通過“子類中的名字定義隱藏所有基類中的同名定義”規(guī)則而避免。如果需要在子類中使用一個(gè)在基類中定義卻被子類的同名定義所隱藏的名字,我們可以使用作用域分解操作符“::”來達(dá)到這個(gè)目的。
#include <iostream>
#include <string>
using namespace std;
class Human
{
public:
string m_name;
protected:
int m_age;
public:
Human(string name,int age):m_name(name),m_age(age)
{
}
};
class Person:public Human
{
private:
string m_name;
int m_age;
public:
Person(string name,int age):Human(name+"Human",age-10),m_name(name),m_age(age)
{
}
void show()
{
cout<<"Person:\n"<<m_name<<"\t"<<m_age<<endl;
cout<<"Human:\n"<<Human::m_name<<"\t"<<Human::m_age<<endl;
}
};
int main(int argc,char** argv)
{
Person p("wxx",100);
p.show();
return 0;
}
繼承方式對(duì)成員訪問控制的影響
- 類成員的訪問控制
| 訪控限定符 | 訪控屬性 | 基類 | 子類 | 外部 | 友元 |
|---|---|---|---|---|---|
| public | 公有成員 | ? | ? | ? | ? |
| protected | 保護(hù)成員 | ? | ? | ? | ? |
| private | 私有成員 | ? | ? | ? | ? |
AcessControl.cpp、AcessControl02.cpp
- 基類中的公有、保護(hù)和私有成員,在其公有、保護(hù)和私有子類中的訪控屬性,因繼承方式而異。
| 基類中的 | public繼承 | protected繼承 | private繼承 |
|---|---|---|---|
| public member | public | protected | private |
| protected member | protected | protected | private |
| private member | private | private | private |
子類的構(gòu)造與析構(gòu)函數(shù)
- 子類隱式調(diào)用基類構(gòu)造函數(shù)
范例:impbase.cpp
如果子類的構(gòu)造函數(shù)沒有顯式地調(diào)用其基類的構(gòu)造函數(shù),那么系統(tǒng)將會(huì)調(diào)用其基類的無參構(gòu)造函數(shù)。但是請(qǐng)注意,只有在我們?yōu)榛愶@式地提供一個(gè)無參構(gòu)造函數(shù),或者不提供任何構(gòu)造函數(shù)(系統(tǒng)會(huì)提供一個(gè)缺省的無參構(gòu)造函數(shù))的情況下,基類才擁有無參構(gòu)造函數(shù)。This is some text!
- 子類顯式調(diào)用基類構(gòu)造函數(shù)
范例:expbase.cpp
每個(gè)子類的實(shí)例化對(duì)象中都包含其基類的實(shí)例化子對(duì)象,即子類對(duì)象的基類部分。該子對(duì)象是由基類的構(gòu)造函數(shù)創(chuàng)建并初始化的。
- 繼承鏈的構(gòu)造和析構(gòu)順序
- 子類的析構(gòu)函數(shù)在執(zhí)行完其中的析構(gòu)代碼,并析構(gòu)完所有的成員變量以后,會(huì)自動(dòng)調(diào)用其基類的析構(gòu)函數(shù),析構(gòu)該子類對(duì)象中的基類子對(duì)象。
- 通過基類指針析構(gòu)子類對(duì)象,實(shí)際被析構(gòu)的僅僅是子類對(duì)象中的基類子對(duì)象,子類的擴(kuò)展部分將失去被析構(gòu)的機(jī)會(huì),極有可能形成內(nèi)存泄漏
構(gòu)造:先基類后子類
析構(gòu):先子類后基類
范例:ConstructorDestructorOrder.cpp
基類分配資源,基類提供析構(gòu),子類未分配資源,子類無需提供析構(gòu)
范例:DestructorOrder01.cpp基類分配資源,基類提供析構(gòu),子類亦分配資源,子類必須提供析構(gòu)
范例:DestructorOrder02.cpp-
通過基類指針析構(gòu)子類對(duì)象的問題
范例:DestructorOrder03.cpp#error 這是一個(gè)錯(cuò)誤例子。
圖示:
基類指針析構(gòu)子類對(duì)象
通過基類指針析構(gòu)子類對(duì)象,實(shí)際被析構(gòu)的僅僅是子類對(duì)象中的基類子對(duì)象,子類的擴(kuò)展部分將失去被析構(gòu)的機(jī)會(huì),極有可能形成內(nèi)存泄漏
子類的拷貝構(gòu)造與拷貝賦值
子類沒有定義拷貝賦值運(yùn)算符函數(shù),編譯器為子類提供的缺省拷貝賦值運(yùn)算符函數(shù),會(huì)自動(dòng)調(diào)用其基類的拷貝賦值運(yùn)算符函數(shù),復(fù)制該子類對(duì)象中的基類子對(duì)象。
子類定義了拷貝賦值運(yùn)算符函數(shù),但沒有顯式調(diào)用其基類的拷貝賦值運(yùn)算符函數(shù),子類對(duì)象中的基類子對(duì)象將得不到復(fù)制。
子類定義了拷貝賦值運(yùn)算符函數(shù),同時(shí)顯式調(diào)用了其基類的拷貝賦值運(yùn)算符函數(shù),子類對(duì)象中的基類部分和擴(kuò)展部分一起被復(fù)制。
參考:copy.cpp、copy02.cpp
子類的操作符重載
在為子類提供操作符重載定義時(shí),往往需要調(diào)用其基類針對(duì)該操作符所做的重載定義,完成部分工作
通過將子類對(duì)象的指針或引用向上造型為其基類類型的指針或引用,可以迫使針對(duì)基類的操作符重載函數(shù)在針對(duì)子類的操作符重載函數(shù)中被調(diào)用
friend ostream& operator<<(ostream& os,const Truck& truck)
{
os<<(Car&)truck<<","<<truck.m_maxCargoWeight; //OK
// os<<(Car)truck<<","<<truck.m_maxCargoWeight; //OK
return os;
}
參考:operator.cpp
名字隱藏與重載
繼承不會(huì)改變類成員的作用域,基類的成員永迖都是基類的成員,并不會(huì)因?yàn)槔^承而變成子類的成員。
因?yàn)樽饔糜虻牟煌?分別在子類和基類中定義的同名成員函數(shù)(包括靜態(tài)成員函數(shù)),并不構(gòu)成重載關(guān)系,相反是一種隱藏關(guān)系,除非通過using聲明將基類的成員函數(shù)引入子類的作用域,形成重載。
任何時(shí)候,無論在子類的內(nèi)部還是外部,總可以通過作用域限定操作符“::”,顯示地調(diào)用那些在基類中定義卻為子類所隱藏的成員函數(shù)
私有繼承與保護(hù)繼承
私有繼承亦稱實(shí)現(xiàn)繼承,旨在于子類中將其基類的公有和保護(hù)成員私有化,既禁止從外部通過該子類訪問返些成員,也禁止在該子類的子類中訪問返些成員
保護(hù)繼承是一種特殊形式的實(shí)現(xiàn)繼承,旨在于子類中將其基類的公有和保護(hù)成員迕行有限的私有化,叧禁止從外部通過該子類訪問返些成員,但并不禁止在該子類的子類中訪問返些成員
多重繼承
在前面的例子中,子類都只有一個(gè)基類,稱為單繼承。除此之外,C++也支持多繼承,即一個(gè)子類可以有兩個(gè)或多個(gè)基類。
好像然并卵的樣子,繼承樹越復(fù)雜越難以維護(hù),Objective-c和Java中都沒有這樣的特性。了解即可(擼主不用這樣的玩意)。
多重繼承語法使用“,”(英文逗號(hào)分割)。就像這樣:
//??
class Dog
{
};
//獅子
class Lion
{
};
//獅子??,呵呵。
class LionDog:public Lion,public Dog
{
};
多重繼承下的構(gòu)造函數(shù)
多繼承的子類和單繼承的子類構(gòu)造函數(shù)基本相同,如下。至于基類排序順序隨意。構(gòu)造時(shí)的順序按照基類排序順序,先基類后子類。
LionDog(/*參數(shù)列表*/): Dog(/*參數(shù)列表*/), Lion(/*參數(shù)列表*/)
{
//初始化
}
多重繼承內(nèi)存示意圖:

參考:code
多重繼承二意性
場(chǎng)景:Staff中有成員std::string name,Leader中也有成員std::string name,當(dāng)HRLeader類繼承Staff和Leader。如果子類中沒做隱藏,那么就會(huì)產(chǎn)生二義性。
例子:code
虛基類
多繼承時(shí)很容易產(chǎn)生命名沖突,即使我們很小心地將所有類中的成員變量和成員函數(shù)都命名為不同的名字,命名沖突依然有可能發(fā)生,比如非常經(jīng)典的菱形繼承層次。如下圖所示:

在一個(gè)子類中保留間接基類的多份同名成員,雖然可以在不同的成員變量中分別存放不同的數(shù)據(jù),但大多數(shù)情況下這是多余的:因?yàn)楸A舳喾莩蓡T變量不僅占用較多的存儲(chǔ)空間,還容易產(chǎn)生命名沖突,而且很少有這樣的需求。
為了解決這個(gè)問題,C++提供了虛基類,使得在派生類中只保留間接基類的一份成員。

參考:Code



