這是C++類重新復(fù)習(xí)學(xué)習(xí)筆記的第 七 篇巷蚪,同專題的其他文章可以移步:http://www.reibang.com/nb/39156122
類繼承的語法
類的繼承允許通過繼承的方式生成新類魁索,繼承自的類為基類抒巢,繼承自基類的類成為派生類互亮,類的繼承寫法如下:
class derivedClass : public/protect/private baseClass
{
// statements
}
其中基類前有一個訪問限定符寿烟,不寫的時候默認(rèn)為private淑际,但是我們主要使用public脸侥,又稱作公有繼承建邓,三個不同的訪問限定詞的區(qū)別在于:
- 公有繼承(
public
):當(dāng)一個類派生自公有基類時,基類的公有成員也是派生類的公有成員睁枕,基類的保護(hù)成員也是派生類的保護(hù)成員官边,基類的私有成員不能直接被派生類訪問,但是可以通過調(diào)用基類的公有和保護(hù)成員來訪問 - 保護(hù)繼承(
protecte
): 當(dāng)一個類派生自保護(hù)基類時外遇,基類的公有和保護(hù)成員將成為派生類的保護(hù)成員 - 私有繼承(
private
):當(dāng)一個類派生自私有基類時注簿,基類的公有和保護(hù)成員將成為派生類的私有成員
例如我們定義一個基類再定義一個派生類:
class baseClass
{
private:
baseInt;
baseDouble;
public:
baseClass(const int pInt = 1, cosnt double pDouble = 2.5);
int function(double d);
}
baseClass :: baseClass(const int pInt = 1, cosnt double pDouble = 2.5)
{
baseInt = pInt;
baseDouble = pDouble;
}
baseClass :: function(double d)
{
cout << d;
return 1;
}
class derivedClass : public baseClass
{
private:
derivedChar;
public:
derivedClass(const int pInt, const double pDouble, const char pChar);
void anotherFunction();
}
derivedClass :: derivedClass(const int pInt, const double pDouble, const char pChar) : baseClass(pInt,pDouble)
{
derivedChar = pChar;
}
void derivedClass :: anotherFunction()
{
cout << "I am the derived class.";
}
從這個例子中,我們可以看到跳仿,基類有兩個私有變量诡渴,baseInt
和baseDouble
,兩個方法菲语,一個構(gòu)造函數(shù)用于給兩個私有變量賦值妄辩,一個用于充數(shù)。派生類繼承自基類山上,于是派生類derivedClass
就擁有了基類的兩個公有方法眼耀,但是它不能訪問基類的兩個私有變量。派生類又定義了自己的一個新私有變量佩憾,同時有自己的構(gòu)造函數(shù)和一個用于充數(shù)的函數(shù)哮伟「苫ǎ可見繼承的作用在于方便地進(jìn)行代碼的重用以及組織管理項目設(shè)計。
基類和派生類的關(guān)系
類的繼承是“is-a”的關(guān)系楞黄,或者說是“is-a-kind-of”池凄,即派生類對象也是一個基類對象。
- 派生類是可以調(diào)用基類的
protected
和public
修飾的成員變量和方法的鬼廓,而派生類也可以定義自己的變量和方法修赞。 - 同時,派生類還可以重載基類的方法桑阶,即聲明一個和基類中相同名稱的成員變量柏副,但是在派生類中對其進(jìn)行重新的定義。如果基類和派生類同時擁有同名同變量參數(shù)同返回值但是定義不同的函數(shù)蚣录,在使用基類對象調(diào)用該函數(shù)時割择,調(diào)用的是基類的函數(shù),在使用派生類的對象調(diào)用該函數(shù)時萎河,調(diào)用的是派生類的定義荔泳。
- 基類指針可以在不進(jìn)行顯式類型轉(zhuǎn)換的情況下指向派生類對象;基類引用可以在不進(jìn)行顯式類型轉(zhuǎn)換的情況下引用派生類對象虐杯。C++要求的引用和指針類型與賦給的類型匹配的規(guī)則對繼承來說例外玛歌。然而,這種例外只是單向的擎椰,不可以將基類對象和地址賦給派生類引用和指針支子。基類指針或引用只能用于調(diào)用基類方法达舒。
多態(tài)公有繼承
多態(tài)即一個派生類繼承自一個基類后值朋,希望可以定義一個和基類中相同名稱、參數(shù)列表巩搏、返回值的函數(shù)昨登,但這個函數(shù)的定義卻與基類中的不同,即一種派生類對基類方法的重載贯底。
首先丰辣,派生類可以重載基類的方法,如果派生類使用基類相同的函數(shù)和函數(shù)定義禽捆,那么就不需要再在派生類中聲明該函數(shù)笙什,即共有的函數(shù)需要放在基類中。如果派生類想對基類的函數(shù)進(jìn)行新的定義睦擂,則需要在派生類中對其再次進(jìn)行聲明并定義得湘,定義時也需要表明定義的是那個類的函數(shù)。如 baseClass::function()
和 derivedClass::function()
這樣顿仇。
虛方法(virtual method)淘正,需要使用關(guān)鍵詞 virtual
修飾基類中的函數(shù)摆马,如下面這樣:
virtual void function(int i);
它的作用如下:當(dāng)基類和派生類都有定義過某個相同方法后,我們需要確定調(diào)用的是哪個類下的方法鸿吆,特別是當(dāng)方法是通過引用或指針而不是對象調(diào)用的囤采。
- 如果沒有使用關(guān)鍵字
virtual
,程序?qū)⒏鶕?jù)引用類型或指針類型選擇方法 - 如果使用了關(guān)鍵字
virtual
惩淳,程序?qū)⒏鶕?jù)引用或指針指向的對象的類型來選擇方法 - 如果有派生類重載了基類的方法蕉毯,一般需要將基類的析構(gòu)函數(shù)設(shè)置成virtual的以保證釋放派生類對象時能夠按照正確的順序調(diào)用析構(gòu)函數(shù)
// 不使用virtual
BaseClass baseClass();
DerivedClass derivedClass();
BaseClass & reference1 = baseClass; // 指向baseClass的類型是BaseClass的引用變量
BaseClass & reference2 = derivedClass; // 指向derivedClass的但是類型是BaseClass的引用變量
reference1.function(); // 會根據(jù)引用的類型即BaseClass調(diào)用BaseClass下的function方法
reference2.function(); // 會根據(jù)引用的類型即BaseClass調(diào)用BaseClass下的function方法
// 使用virtual
BaseClass baseClass();
DerivedClass derivedClass();
BaseClass & reference1 = baseClass; // 指向baseClass的類型是BaseClass的引用變量
BaseClass & reference2 = derivedClass; // 指向derivedClass的但是類型是BaseClass的引用變量
reference1.function(); // 會根據(jù)引用指向的類型即BaseClass調(diào)用BaseClass下的function方法
reference2.function(); // 會根據(jù)引用指向的類型即DerivedClass調(diào)用BaseClass下的function方法
抽象基類
抽象基類(abstract base class,ABC)是一種特殊的基類思犁,從概念上講代虾,將所有派生類的公用的方法進(jìn)行抽象匯總聲明(定義)到的一個類中,這種設(shè)計下的類可以視作一個抽象基類激蹲。但是真正的抽象基類應(yīng)該是至少包含了一個純虛函數(shù)(pure virtual function)的類棉磨,這種類不能聲明對應(yīng)的對象,只能作為基類学辱。
純虛函數(shù)是一種只在抽象基類中給出原型乘瓤,但是部給出定義的函數(shù),更像是一個接口策泣,由所有的派生類對純虛函數(shù)根據(jù)自己類的需求來實現(xiàn)其定義衙傀。純虛函數(shù)的寫法是在虛函數(shù)后面以 =0 結(jié)尾
virtual double pureVirtualFunction(int i) const = 0;
應(yīng)用這種方式,可以將所有派生類共有但是卻又各自有著不同實現(xiàn)的方法抽象到一個基類中萨咕,提供其原型但是不對其進(jìn)行定義(也只有純虛函數(shù)C++允許不給出定義)统抬,然后使得各個派生類自己給出其定義。
私有繼承
私有繼承即繼承的基類使用private修飾符修飾的繼承任洞,如果沒有訪問限定符的修飾蓄喇,默認(rèn)也是私有繼承发侵,私有繼承是一種“has-a”的關(guān)系交掏。
class DerivedClass : private BaseClass{ }
class DerivedClass : BaseClass{ }
私有繼承使得基類的公有成員、保護(hù)成員都被成為派生類的私有成員刃鳄,這就使得基類的那些方法都不能再被派生類的實例化對象使用盅弛,而只能被派生類的成員函數(shù)在類內(nèi)部使用。即派生類部繼承基類的接口叔锐。
這里比較了三種繼承之間的區(qū)別:
特征 | 公有繼承 | 保護(hù)繼承 | 私有繼承 |
---|---|---|---|
公有成員變成 | 派生類的公有成員 | 派生類的保護(hù)成員 | 派生類的私有成員 |
保護(hù)成員變成 | 派生類的保護(hù)成員 | 派生類的保護(hù)成員 | 派生類的私有成員 |
私有成員變成 | 只能通過基類接口訪問 | 只能通過基類接口訪問 | 只能通過基類接口訪問 |
能否隱式向上轉(zhuǎn)換 | 能 | 只能在派生類中 | 不能 |
多重繼承
多重繼承(Multiple Inheritance)也是“is-a”的關(guān)系挪鹏,它允許一個類繼承自多個類,只需要將繼承的類使用逗號隔開即可愉烙,像下面這樣:
class DerivedClass : public BaseClass1, public BaseClass2 {……}
class DerivedClass : public BaseClass1, BaseClass2 {……} // BaseClass2 is a private base
多重繼承中每一個被繼承的基類都需要設(shè)置訪問限定符讨盒,根據(jù)需要可以使用不同的訪問限定符,不寫默認(rèn)為private
例如設(shè)置一個基類Worker表示工人步责,然后工人可以是歌手也可以是服務(wù)員返顺,我們使用兩個類繼承自這個基類禀苦,Singer和Waiter,最后遂鹊,我們可以定義一個既是歌手有時服務(wù)員的類振乏,所以它同時繼承自Singer和Waiter,他們的關(guān)系就像下邊這樣:
class Worker {……}
class Singer : public Worker {……}
class Waiter : public Worker {……}
class SingingWaiter : public Singer, public Waiter {……}
虛基類
首先多重繼承導(dǎo)致了一個問題就是秉扑,當(dāng)一個SingingWaiter的實例繼承自Singer和Waiter時慧邮,也就間接地兩次繼承了Worker,也就是說一個SingingWaiter的實例結(jié)構(gòu)應(yīng)該是如下這樣的:
這樣引發(fā)的問題就是舟陆,當(dāng)我們把派生類對象的地址賦給一個基類的指針時就無法區(qū)分是賦給哪個基類误澳,導(dǎo)致二義性:
SingingWaiter sw;
Worker * psw = &sw;
因為sw中包含兩個Worker對象秦躯,從而有兩給地址可以選擇脓匿,于是正確的寫法應(yīng)該是:
Worker * psw1 = (Waiter *) &sw;
Worker * psw2 = (Singer *) &sw;
所以虛基類(Virtual Base Classes)將解決這個問題。虛基類使得從多個類派生出的對象只繼承一個基類對象宦赠,需要在類繼承的聲明中使用virtual關(guān)鍵詞陪毡,virtual和public的次序無所謂:
class Singer : virtual public Worker {……}
class Waiter : public virtual Worker {……}
class SingingWaiter : public Singer, public Waiter {……}
現(xiàn)在,SingingWaiter對象就只包含Worker對象的一個副本勾扭,從本質(zhì)上說是毡琉,繼承的Singer和Waiter共享一個Worker對象,而不是各自引入一個Worker對象的副本妙色,從而可以使用多態(tài)桅滋。
轉(zhuǎn)載請注明出處,本文永久更新鏈接:https://blogs.littlegenius.xin/2019/08/28/【C-溫故知新】七類的繼承/