繼承小結

一、繼承概念
繼承(inheritance)機制是面向對象程序設計使代碼可以重複使用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱派生類。一個派生類可以從一個基類派生,也可以從多個基類派生。從一個基類派生的繼承稱爲單繼承;從多個基類派生的繼承稱爲多繼承。
繼承,通俗的講可以看做遺傳,兒子具有和父親相同的部分我們通常稱爲是從父親那兒遺傳得來的。因此繼承所得的派生類也稱爲子類,而原始的基類也可以稱爲父類。
二、繼承定義格式
這裏寫圖片描述
繼承定義時,須說明繼承的類型,若不加類型的說明,class默認private繼承,struct默認public繼承。派生類的內容爲新增的方法和派生類自己的成員變量。
eg:

class A
{
public:
    int a = 0;
    int b = 0;
private:
    void FunTest()
    {}
};
class B :public A
{
public:
    int c = 0;
private:
    void FunTest1()
    {}
};

現在可以新建一個B的對象訪問A中的成員
這裏寫圖片描述
當然,這裏只能訪問public類型的成員,接下來就看一下派生類可以訪問的類型。
三、繼承關係與訪問限定符
這裏寫圖片描述
注:①public繼承是一個接口繼承,保持is-a原則,每個父類成員對子類也可用,protected/private繼承是實現繼承,基類的部分成員並非完全成爲子類接口的一部分,是has-a關係原則。
②protected繼承在派生類中基類的所有成員訪問限定符權限降級,保護降爲保護,派生類外無法訪問。private繼承,基類中除了私有成員外所有成員被當做私有,派生類外無法訪問。
③不管那種繼承方式,在派生類內部都可以訪問到基類的共有成員和保護成員。

四、派生類的默認成員函數
在繼承關係裏面,在派生類中如果沒有顯示定義這六個成員函數,編譯系統則會默認合成這六個默認的成員函數。
這裏寫圖片描述
繼承關係中構造函數和析構函數的調用順序
我們來看一段代碼:

class B
{
public:
    B(int a)
    {
        cout << "B()" << endl;
    }
    ~B()
    {
        cout << "~B()" << endl;
    }
};
class D :protected B
{
public:
    D()
        :B(2)
    {
        cout << "D()" << endl;
    }
    ~D()
    {
        cout << "~D()" << endl;
    }
};

void FunTest()
{
    //B b;
    D d;
}
int main()
{
    FunTest();
    system("pause");
    return 0;
}

這裏寫圖片描述
通過運行結果,我們就可以清晰的看出先調用的是基類的構造函數,其實應該先調用的是派生類的構造函數但是我們顯示定義了基類的構造函數,所以先執行基類構造函數的函數體。
注:①、基類沒有缺省構造函數,派生類必須要在初始化列表中顯式給出基類名和參數列表。
②、基類沒有定義構造函數,則派生類也可以不用定義,全部使用缺省構造函數。
③、基類定義了帶有形參表構造函數,派生類就一定定義構造函數。

五、繼承體系的作用域
①. 在繼承體系中基類和派生類是兩個不同作用域。
②. 子類和父類中有同名成員,子類成員將屏蔽父類對成員的直接訪問。(在子類成員函數中,可以使用 基類::基類成員 訪問)
③. 注意在實際中在繼承體系裏面最好不要定義同名的成員。
六、賦值兼容規則
①. 子類對象可以賦值給父類對象(切割/切片)
②. 父類對象不能賦值給子類對象
③. 父類的指針/引用可以指向子類對象
④. 子類的指針/引用不能指向父類對象(可以通過強制類型轉換完成)
這裏寫圖片描述
父類對象也可以通過強制類型轉換達到給子類對象賦值的目的,但是存在風險,

class A
{
public:
    int a = 0;
    int b = 0;
private:
    void FunTest()
    {}
};
class B :public A
{
public:
    int c = 0;
private:
    void FunTest1()
    {}
};
int main()
{
    B b;
    A a;
    a = b;
    b = *(B*)&a;
    system("pause");
    return 0;
}

七、單繼承、多繼承和菱形繼承
1、單繼承:一個子類只有一個直接父類時稱這個繼承關係爲單繼承。

person類只有student類繼承,student類只有graduate類繼承,person類和student類之間的關係即爲單繼承關係。
2、多繼承:一個子類有兩個或以上直接父類時稱這個繼承關係爲多繼承。
這裏寫圖片描述
Assistant類同時繼承student和teacher兩個類,他們之間的繼承關係爲多繼承關係。
3、菱形繼承:A類同時被B1、B2兩個類繼承,C類同時繼承B1、B2。
這裏寫圖片描述

class A
{
public:
    int _a;
};
class B1 :public A
{
public:
    int _b1;
};
class B2 :public A
{
public:
    int _b2;
};
class C :public B1, public B2
{
public:
    int _c;
};
void Funtest()
{
    C c;
    c._a = 0;//只能訪問到基類,不能訪問到基類的基類
    c.B1::_a = 0;
    c.B2::_a = 0;
}
int main()
{
    return 0;
}

菱形對象模型
這裏寫圖片描述
c 對象裏有兩份_a 成員,菱形繼承容易產生二義性和數據冗餘的問題,而虛擬繼承能解決這個問題。
八、虛擬繼承

class A
{
public:
    int _a;
};
class B1 :virtual public A
{
public:
    int _b1;
};
class B2 :virtual public A
{
public:
    int _b2;
};
class C :public B1, public B2
{
public:
    int _c;
};

這就是一個虛擬繼承的例子啦,在B1、B2 的繼承類型前面加上關鍵字virtual,C類會產生一個A方法的拷貝。通過用關鍵字virtual 修正一個基類的聲明可以將它指定爲被虛擬派生。對於子對象及其非靜態成員的訪問是間接進行的,這使得在多繼承情況下,把多個虛擬基類子對象組合成派生類中的一個共享實例,從而提供了靈活性。
普通成員函數調用時是根據類名和函數名在代碼區查找相應的函數代碼,而虛函數則會通過虛函數表,然後通過偏移量找到真正的函數。
當然,還有幾點需要注意:
1、友元關係不能繼承,也就是說基類友元不能訪問子類私有和保護成員。
2、基類定義了static成員,則整個繼承體系裏面只有一個這樣的成員。無論派生出多少個子類,都只有一個static成員實例。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章