繼承

繼承

繼承的基本概念和語法

  1. 子類繼承基類,基類派生子類,子類IsA基類。
  2. class 子類名 : 繼承方式描述符1 基類名1, 繼承方式描述符2 基類名2, ...
    {
    };
class Staff
{
}
class SoftwareEngineer:public Staff
{
}
class ProductManager:public Staff
{
}
  1. 繼承方式:公有繼承(public)、保護(hù)繼承(protected)、私有繼承(private)。

范例:Staff.cpp

公有繼承

  1. 一個(gè)子類類型的對(duì)象在任何時(shí)候都可以作為一個(gè)基類類型的對(duì)象,而不必使用顯示的類型轉(zhuǎn)換,前提是兩者(子類及其基類)都是通過指針或引用操作的。

    示意圖

    范例:Staff02.cpp

  2. 我們?cè)谧宇愔锌梢灾苯邮褂没惖乃泄泻捅Wo(hù)成員,就象它們是在子類中聲明的那樣,但基類的私有成員在子類中雖然存在卻不可見,故無法直接使用。

class A
{
      string name;
};
//
class B
{
      int age
public:
      B()
      {
        #error 子類不能訪問父類中的私有成員。
        this->name = "wxx";
      }
};
  1. 盡管基類的公有和保護(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

AcessControl.cpp

子類的構(gòu)造與析構(gòu)函數(shù)

  1. 子類隱式調(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!

  1. 子類顯式調(diào)用基類構(gòu)造函數(shù)
    范例:expbase.cpp

每個(gè)子類的實(shí)例化對(duì)象中都包含其基類的實(shí)例化子對(duì)象,即子類對(duì)象的基類部分。該子對(duì)象是由基類的構(gòu)造函數(shù)創(chuàng)建并初始化的。

  1. 繼承鏈的構(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
  1. 基類分配資源,基類提供析構(gòu),子類未分配資源,子類無需提供析構(gòu)
    范例:DestructorOrder01.cpp

  2. 基類分配資源,基類提供析構(gòu),子類亦分配資源,子類必須提供析構(gòu)
    范例:DestructorOrder02.cpp

  3. 通過基類指針析構(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)造與拷貝賦值

  1. 子類沒有定義拷貝賦值運(yùn)算符函數(shù),編譯器為子類提供的缺省拷貝賦值運(yùn)算符函數(shù),會(huì)自動(dòng)調(diào)用其基類的拷貝賦值運(yùn)算符函數(shù),復(fù)制該子類對(duì)象中的基類子對(duì)象。

  2. 子類定義了拷貝賦值運(yùn)算符函數(shù),但沒有顯式調(diào)用其基類的拷貝賦值運(yùn)算符函數(shù),子類對(duì)象中的基類子對(duì)象將得不到復(fù)制。

  3. 子類定義了拷貝賦值運(yùn)算符函數(shù),同時(shí)顯式調(diào)用了其基類的拷貝賦值運(yùn)算符函數(shù),子類對(duì)象中的基類部分和擴(kuò)展部分一起被復(fù)制。
    參考:copy.cppcopy02.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

名字隱藏與重載

  1. 繼承不會(huì)改變類成員的作用域,基類的成員永迖都是基類的成員,并不會(huì)因?yàn)槔^承而變成子類的成員。

  2. 因?yàn)樽饔糜虻牟煌?分別在子類和基類中定義的同名成員函數(shù)(包括靜態(tài)成員函數(shù)),并不構(gòu)成重載關(guān)系,相反是一種隱藏關(guān)系,除非通過using聲明將基類的成員函數(shù)引入子類的作用域,形成重載。

  3. 任何時(shí)候,無論在子類的內(nèi)部還是外部,總可以通過作用域限定操作符“::”,顯示地調(diào)用那些在基類中定義卻為子類所隱藏的成員函數(shù)

私有繼承與保護(hù)繼承

  1. 私有繼承亦稱實(shí)現(xiàn)繼承,旨在于子類中將其基類的公有和保護(hù)成員私有化,既禁止從外部通過該子類訪問返些成員,也禁止在該子類的子類中訪問返些成員

  2. 保護(hù)繼承是一種特殊形式的實(shí)現(xiàn)繼承,旨在于子類中將其基類的公有和保護(hù)成員迕行有限的私有化,叧禁止從外部通過該子類訪問返些成員,但并不禁止在該子類的子類中訪問返些成員

  3. 私有子類和保護(hù)子類類型的指針或引用,不能隱式轉(zhuǎn)換為其基類類型的指針或引用
    參考:1、23

多重繼承

在前面的例子中,子類都只有一個(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)存示意圖:

l_i1.png

參考:code

多重繼承二意性

場(chǎng)景:Staff中有成員std::string name,Leader中也有成員std::string name,當(dāng)HRLeader類繼承StaffLeader。如果子類中沒做隱藏,那么就會(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容