天天看點

C++——繼承(單繼承、多繼承、菱形繼承)&&虛繼承&&虛基類

首先,我們得知道,面向對象的三大語言:封裝 繼承 多态

今天我們主要談談繼承

1.什麼是繼承

——子類(派生類)可以通路和使用父類(基類)的成員

比如:有兩個類,A和B,我們在定義時,使得B 可以通路 A 的成員,我們叫做 B繼承了A。

B為子類,(派生類);A為父類(基類)

2.為什麼要用繼承呢 ?

代碼分析:

#include<iostream>
using namespace std;

class A
{
public:
    void fun1(){}
    void fun2(){}
};

class B :public A
{
public:
    void fun3(){}
    void fun4(){}
};

 int main()
{
    B b;
    b.fun1();
    b.fun2();
    b.fun3();
    b.fun4();
    return ;

}
           

繼承是為了提高程式的複用性。

如上述代碼,不需要額外定義fun1( )和fun2(),直接從父類繼承即可。

3.三種繼承關系

  • 成員通路限定符&繼承關系:
C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類
  • 三種繼承關系下基類成員在派生類的通路關系變化:
C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類
  • 問題1:為什麼已經有了私有成員還要有保護成員?

答:基類的私有成員在派生類中是無法被通路的,但如果一些基類成員,不想被基類對象直接通路,需要在派生類中通路,就定義為保護成員。保護成員限定符是為了繼承而生的。

  • 問題2:public / protected / private繼承之間的差別是什麼?
public繼承是一個接口繼承,保持 is-a 原則,(我是一個你)
每個父類可用的對象對子類也可用,每個子類對象也是一個父類對象。

protected / private 繼承是一個實作繼承,是 has -a 的關系原則,(我有一個你)
基類的部分成員,并未完全成為子類接口的一部分。
           
  • 問題3:什麼是成員不可見?

    答:不管是哪種繼承方式,在派生類内部都可以通路基類的共有成員和保護成員,基類的私有成員存在,但是在派生類中無法通路。(不可見)

  • 使用關鍵字class時預設的繼承方式是private,使用struct時預設的繼承方式是public,不過最好顯示的寫出繼承方式。

4.繼承規則

  • A B兩個類不相關,不能繼承,能發生繼承關系的兩個類必須具有相關性。
  • 從邏輯上看,B是A 的一種,可以繼承(比如:男人是人的一種,男人可以繼承人的特性)
  • 從邏輯上看,B是A 的一種,并且 A 的功能和屬性對B 有意義,才可以繼承。(比如:鴕鳥是鳥的一種,但是鴕鳥不會飛,鴕鳥将這個功能繼承過來毫無意義)

5.繼承與轉換——指派相容規則——public繼承

  • 子類對象可以指派給父類對象(切割/切片)
  • 父類對象不可以指派給子類對象
  • 父類的指針/ 引用可以指向子類對象
  • 子類的指針/ 引用不能指向父類對象(可以通過強制類型轉換實作)

代碼分析:

class Person
{
public:
    void Show()
    {
        cout<<_name << endl;

    }
protected:
    string _name;
private:
    int _age;

};






//class Student :private Person
//class Student :protected Person

class Student :public Person
{
public:int _num;
};

int main()
{
    Person p;
    Student s;

    p = s;//1.子類對象可以直接指派給父類對象(切割/切片)

    //s = p;//2.父類對象不能直接指派給子類對象(空間不足)


          //3.父類的指針/引用,可以指向子類對象
    Person *p1 = &s;

    Person&r = s;//不加const也能引用,——天然支援


          //4.子類的指針/引用不能指向父類的對象(但可以通過強制轉換實作)
    Student*p2 = (Student*)&p;
    Student&r2 = (Student&)p;

    //const Student &r2 = p;//編不過:天然不支援





    system("pause");
        return ;

}
           
C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類
C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類

6.繼承體系中的作用域:

  • 在繼承體系中,基類和派生類都有獨立的作用域
  • 子類和父類中有同名成員,子類成員将屏蔽父類對成員的直接通路。

    (在子類成員函數中,可使用基類::基類成員 )通路 ——隐藏(重定義)

  • 注意:在實際中最好不要定義同名成員。

7.單繼承&&多繼承&&菱形繼承

  • 單繼承:一個子類隻有一個直接父類時稱這個繼承關系為單繼承。
  • 多繼承:一個子類有兩個或兩個以上直接父類稱這個繼承關系為多繼承。
    C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類
  • 菱形繼承
    C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類

菱形繼承存在問題:二義性和 資料備援(虛繼承解決)

C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類

**注:

1.虛基表中存的是一個偏移量(相對位址,而不是實際位址)**

2.同類型對象共享一個虛基表(偏移量相同)

3.虛基表的使用:節省空間

8.虛繼承

代碼分析:

//不是虛繼承時
#include<iostream>
using namespace std;

class A
{
public:
    int _a;
};

class B:public A
{
public:
    int _b;
};

class C :public A
{
public:
    int _c;
};

class D:public B,public C
{
public :
    int _d;
};

int main()
{
    D dd;
    cout << sizeof(dd) << endl;
    dd.B::_a = ;
    dd._b= ;
    dd.B::_a = ;
    dd._c = ;
    dd._d = ;

    B bb;
    C cc;
    system("pause");
    return ;


}
           

運作結果:

C++——繼承(單繼承、多繼承、菱形繼承)&amp;&amp;虛繼承&amp;&amp;虛基類

結果分析:

D中一共隻有四個變量,但此時sizeof 的結果為20,也就是說有5個值,那這五個值是從哪來的呢?

——實際上D繼承了兩個_a, B繼承的_a 和 A繼承的_a是不一樣的,無法确定哪一個值應該是_a,是以系統預設有兩個_a。(有歧義)

在監視視窗上看到,程式的執行步驟為:

  1. B中的_a 變成 1
  2. D中的_b 變成 3
  3. C中的_a 變成 2
  4. D中的_c 變成 4
  5. D中的_d 變成 5

當繼承方式為虛繼承時:

class B:virtual public A
{
public:
    int _b;
};

class C :virtual public A
{
public:
    int _c;
};
           

當繼承方式為虛繼承時,程式的執行過程為:

  1. B中的_a 和 C中的 _a 同時變為1
  2. D中的_b 變成 3
  3. B中的_a 和 C中的 _a 同時變為2
  4. D中的_c 變成 4
  5. D中的_d 變成 5

注:此時A 叫做虛基類,當子類對父類的繼承方式為虛繼承時,父類就叫做虛基類。

虛基類的作用:當一個基類被聲明為虛基類後,即便它被多次繼承,最後的派生類中也隻有它的一個備份

參照上述代碼,A被聲明為虛基類,D 既被B繼承,又被 C繼承 。但最後的派生類D中隻有一個_a

——如此便解決了資料備援和二義性的問題

c++

繼續閱讀