1. 程式人生 > >【C++】——繼承方式

【C++】——繼承方式

一、 繼承的概念相關:

1、概念
在C++語言中,一個派生類可以從一個基類派生,也可以從多個基類派生。從一個基類派生的繼承稱為單繼承;從多個基類派生的繼承稱為多繼承。
繼承機制是面向物件程式設計使程式碼可以複用的最重要的手段,它允許程式設計師在保持原有類特性的基礎上進行擴充套件,增加功能。這樣產生的新類稱派生類。
2、格式:


3、作用:
1、)程式碼複用
2、)實現多型

二、程式碼分析繼承

1、一段簡單的程式碼展示繼承

class Base
{
private:
    int _pri;
protected:
    int _pro;
public
: int _pub; }; class Derived:public Base { private: int _priD; protected: int _proD; public: int _pubD; }; int main() { cout << sizeof(Derived) << endl;//24 return 0; }

有沒有繼承下來,看派生類的大小即可
這裡寫圖片描述

大小為24,即繼承下來了,
(1)派生類繼承下來基類的公有成員變數也是公有的;
(2)基類的私有成員在基類裡可以訪問,但在派生類裡無法直接訪問,是基類裡的隱私
(3)將基類裡保護的成員繼承下來,要麼是私有的,要麼是保護的——問題?哪一個呢?下面我們就一起研究一下
2、繼承的方式
猜測一下上面的繼承問題
猜測:保護(基類)———private, protected(派生類)
假設:protected—派生一層

class Base
{
public:
    void SetValue(int pri, int pro, int pub)
    {
        _pri = pri;
        _pro = pro;
        _pub = pub;
    }
private:
    int _pri;
protected:
    int _pro;
public:
    int _pub;
};

class Derived:public Base
{
public:
    void SetValueD(int pri, int pro, int pub)
    {

    }
private
: int _priD; protected: int _proD; public: int _pubD; }; //驗證公有的繼承方式 class D:public Derived { public: void FunTest() { _pro = 5; _proD = 10; } }; int main() { cout << sizeof(Derived) << endl; Derived d; d.SetValue(10, 20, 30); return 0; }


(1)——驗證保護的繼承方式

class Base
{
public:
    void SetValue(int pri, int pro, int pub)
    {
        _pri = pri;
        _pro = pro;
        _pub = pub;
    }
private:
    int _pri;
protected:
    int _pro;
public:
    int _pub;
};

class Derived :protected Base
{
public:
    void SetValueD(int pri, int pro, int pub)
    {
        SetValue(pri, pro, pub);//此函式繼承下來是保護的(私有的調用不了)
    }
private:
    int _priD;
protected:
    int _proD;
public:
    int _pubD;
};
int main()
{
    cout << sizeof(Derived) << endl;
    Derived d;
    d.SetValue(10, 20, 30);
    return 0;
}


此函式在基類許可權是公有的的,但通過保護繼承,在類外不允許呼叫,說明派生類繼承下來時該成員函式的許可權發生了改變(要麼是私有的,要麼是保護的)。
下面來驗證一下
驗證方法1、

驗證方法2、再派生一層,再最底層的派生類中呼叫

驗證結果為基類公有的成員函式、成員變數,以保護的繼承方式繼承下來,派生類中變為保護的(基類中是保護的繼承下來也是保護的)
(2)——私有的繼承方式
基類中公有的成員被私有的繼承方式繼承下來,在派生類中變成了私有的
——驗證方法:再派生一層,在派生類中已經變為私有的,在最底層派生類中不可訪問

class Base
{
public:
    void SetValue(int pri, int pro, int pub)
    {
        _pri = pri;
        _pro = pro;
        _pub = pub;
    }
private:
    int _pri;
protected:
    int _pro;
public:
    int _pub;
};

class Derived :private Base
{
public:
    void SetValueD(int pri, int pro, int pub)
    {

    }
private:
    int _priD;
protected:
    int _proD;
public:
    int _pubD;
};

//驗證私有的繼承方式
class D :public Derived//再派生一層,在派生類中已經變為私有的,在最底層派生類中不可訪問
{
public:
    void FunTest()
    {
        _pub = 10;
    }
};

綜合以上的程式碼分析總結繼承許可權和訪問限定符如下
這裡寫圖片描述
(注意:不可見即不可訪問)
總的來說一句話:public —> protected ——> private
如果超過了本身,則繼承下來的降一級,否則保持不變。
總結:
(1)基類private成員在派生類中是不能被訪問的,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義protected,可以看出保護成員限定符是因繼承才出現的
(2)public繼承是一個介面繼承,保持is-a原則,每個父類可用的成員對子類也可用,因為每個子類物件也都是一個父類物件
(3)protected/private繼承是一個實現繼承,基類的部分成員並非完全成為子類介面的一部分,是 has-a 的關係原則,所以非特殊情況下
不會使用這兩種繼承關係,在絕大多數的場景下使用的都是公有繼承。私有繼承意味著is-implemented-in-terms-of(是根據……實現的)。通常比組合(composition)更低階,但當一個派生類需要訪問基類保護成員或需要重定義基類的虛擬函式時它就是合理的
(4)不管是哪種繼承方式,在派生類內部都可以訪問基類的公有成員和保護成員,基類的私有成員存在但是在子類中不可見(不能訪問)
(5)使用關鍵字class時預設的繼承方式是private,使用struct時預設的繼承方式是public,不過最好顯示的寫出繼承方式
(6)在實際運用中一般使用都是public繼承,極少場景下才會使用protetced/private繼承

三、派生類物件的構造和析構

1、繼承體系下派生類和基類建構函式的呼叫次序
建立哪個類的物件就呼叫哪個類的建構函式
——建立派生類物件
呼叫派生類建構函式——>在派生類建構函式初始化列表構造基類部分B()——>構造派生類自己特有的成員——>執行派生類建構函式的函式體
2、繼承體系下派生類和基類解構函式的呼叫次序
——銷燬派生類物件
呼叫派生類的解構函式——>銷燬派生類自己管理的資源——>呼叫基類的解構函式——>銷燬基類管理的資源
【說明】
(1)基類沒有預設建構函式,派生類必須要在初始化列表中顯式給出基類名和引數列表
(2)基類沒有定義建構函式,則派生類也可以不用定義,全部使用預設建構函式
(3)基類定義了帶有形參表建構函式,派生類就一定定義建構函式

四、繼承體系中的作用域以及賦值相容規則

1、作用域
(1)在繼承體系中基類和派生類是兩個不同作用域
(2)子類和父類中有同名成員,子類成員將遮蔽父類對成員的直接訪問(在子類成員函式中,可以使用 基類::基類成員 訪問)–隱藏 –重定義
(3)注意在實際中在繼承體系裡面最好不要定義同名的成員
——同名隱藏(型別無關)
(a)成員變數同名:

class B
{
public:
    int _b;
};
class D :public B
{
public:
    char _b;
};
int main()
{
    D d;
    d._b = 1000000;
    return 0;
}

如果不指定作用域,會賦值給派生類自己的成員

最好在基類和派生類中不要出現相同的成員名,但也是有辦法的
這裡寫圖片描述
是不是就達到了給基類成員賦值的效果了呢
那麼為什麼沒加作用域,會預設賦值給派生類的成員呢?(當然會優先訪問自己的咯)
(b)成員函式同名
優先呼叫派生類的成員函式

class B
{
public:
    void Test()
    {}
public:
    int _b;
};
class D :public B
{
public:
    void Test(int a)
    {}
public:
    char _d;
};
int main()
{
    D d;
    d.Test(10);
    return 0;
}


指明作用域之後呢,和預想結果是一樣的

——函式過載和同名隱藏
(1)函式過載:同一作用域、相同函式名、引數列表不同(個數、型別、次序),和返回值是否相同沒有關係。
(2)同名隱藏:在繼承體系中,基類和派生類 有相同名稱的成員(成員變數和函式,和型別沒有關係),如果使用派生類物件呼叫同名稱的類成員,優先呼叫派生類自己。
2、賦值相容規則
在public繼承許可權下,子類和派生類物件之間有:
(1)子類物件可以賦值給父類物件(切割/切片)
(2)父類物件不能賦值給子類物件
(3)父類的指標/引用可以指向子類物件
(4)子類的指標/引用不能指向父類物件(可以通過強制型別轉換完成)
3、友元與繼承
友元關係不能繼承,也就是說基類友元不能訪問子類私有和保護成員
靜態成員可以繼承

class B
{
    friend void FunTest();//友元——能在函式內訪問B類的私有成員
public:
    int _b;
private:
    int _b1;
};
class D :public B
{
public:
    int _d;
private:
    int _d1;
};

void FunTest()
{
    B b;
    b._b1 = 10;

    D d;
    d._d1 = 20;
}

四、繼承體系下的物件模型

物件模型:物件中非靜態成員變數在記憶體中的佈局形式
1、 單繼承:一個類只有一個基類(前面的就是單繼承的例子,)

class B
{
public:
       int _b;
};
class D: public B
{
public:
   Int _d;
};
物件模型:
D:(基類中成員在上+派生類自己特有的成員在下)
_b—>B
_d—>派生類D自己的成員

2、 多繼承:一個類有多個基類(先繼承哪個類,哪個就在前面)

class B1
{
public:
    int _b1;
};

class B2
{
public:
    int _b2;
};

class D: public B1, public B2
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;

    D d;
    d._b1 = 0;
    d._b2 = 1;
    d._d = 2;
    return 0;
}
文字描述物件模型:
D:
_b1—>B1
_b2—>B2
_d—>派生類D自己特有的

3、 菱形繼承:單繼承+多繼承(二義性問題)

這裡寫圖片描述

class B
{
public:
    int _b;
};

class C1:public B
{
public:
    int _c1;
};

class C2 :public B
{
public:
    int _c2;
};

class D :public C1, public C2
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;//20
    D d;
    d.C1::_b = 0;
    d._c1 = 1;
    d._c2 = 2;
d.C2::_b = 3;
    d._d = 4;
    return 0;
}
文字描述物件模型:
   D:C1(B + c1)+ C2(B + c2)+ _d
   C1:_b—>B
      _c1—>C1自己特有的
   C2:_b—>B
      _c2—>C2自己特有的
      _d—>派生類D自己的


菱形繼承物件模型

4、 虛擬繼承:(多了4位元組)

class B
{
public:
    int _b;
};

class D : virtual public B
{
public:
    int _d;
};
int main()
{
    cout << sizeof(D) << endl;//12

    D d;
    d._b = 1;
    d._d = 2;
    return 0;
}

派生類D的大小——》4+B+_d =====》12
派生類D的模型:
和普通繼承的不同:基類的部分到後面去了

派生類虛擬繼承時,編譯器給它合成了一個建構函式

多了4位元組—實際上是指向了偏移量表格的地址,偏移量表格里面有兩部分內容:物件相對於自己的偏移量0+基類成員相對於物件起始位置的偏移量8
5、 菱形虛擬繼承:

//即用虛擬繼承解決菱形繼承中存在的二義性
class B
{
public:
    int _b;
};

class C1 : virtual public B
{
public:
    int c1;

};

class C2 :virtual public B
{
public:
    int c2;
};

class D : public C1,public C2
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;//24

    D d;
    d._b = 1;
    d.c1 = 2;
    d.c2 = 3;
    d._d = 4;
    return 0;
}

派生類的物件模型:

關於多型的部分在後續會發出來