天天看點

cpp繼承中的那些事(上)

0x00 背景介紹

我們都知道(确性),面向對象的三大特性是封裝、繼承、多态,封裝是基礎,繼承是關鍵,多态是補充,看吧,繼承是關鍵,這麼關鍵的東西我們當然有必要好好了解一下啦。

0x01 單繼承和多繼承

顧名思義,單繼承就是派生類隻有一個直接基類的繼承方式,而多繼承就是有多個直接基類的繼承方式。而不管是哪種繼承,派生類都擁有父類的所有成員(成員資料和成員函數)

#include <iostream>
class FATHER {
private:
    int fa = 10;
};
class MONTHER {
private:
  int mo = 20;
};
// 單繼承
class SON:public FATHER {

};
// 多繼承
class BABY:public FATHER,public MOTHET {
  
};

int main(){
  SON son;    // 擁有FATHER的所有成員
  BABY baby;  // 擁有FATHER和MOTHER的所有成員
  return 0;
}           

複制

0x02 繼承方式和通路權限

和結構體相比,類就顯得“自私”多了,人家結構體的預設通路權限和繼承方式都是公開(public)的,這樣它的資料在任一時刻任何域中都能被任意對象通路到,類則剛好相反,預設的通路權限和繼承方式都是私有(private)的,也就是隻給特定對象開放通路權限。

{% note warning modern %}這裡的結構體是在c++的文法體系中,c語言的文法中并沒有結構體繼承這種說法。{% endnote %}

繼承方式 基類成員特性 派生類成員特性 派生類對象通路情況
公有繼承(public) publicprotectedprivate publicprotected不可直接通路 可直接通路不可直接通路不可直接通路
保護繼承(protected) publicprotectedprivate protectedprotected不可直接通路 不可直接通路不可直接通路不可直接通路
私有繼承(private) publicprotectedprivate privateprivate不可直接通路 不可直接通路不可直接通路不可直接通路

總結一下:

1.  公有繼承并不會改變基類成員的通路權限,保護繼承和私有繼承會把公有和受保護的通路權限分别修改為受保護的和私有的
2.  隻有共有通路權限下的成員能直接被通路到,其他都不可直接通路           

複制

#include <iostream>
// 基類
class FATHER {
public:
    void setC(int _c) {
        this->c = c;
    }
    int a;
protected:
    int b;
private:
    int c;
};
// 派生類A 公有繼承
class SONA :public FATHER {
public:
    void FunA() {
        a = 200;
        b = 10;
        setC(5);
        //c = 5;基類中是私有的,無論怎麼繼承都不能通路
    }
};
// 派生類B 保護繼承
class SONB :protected FATHER {
public:
    void FunA() {
        a = 200;
        b = 10;
        //c = 5;父類中是私有的,無論怎麼繼承都不能通路
    }
};
// 派生類C 私有繼承
class SONC :private FATHER {
public:
    void FunA() {
        a = 200;
        b = 10;
        //c = 5;父類中是私有的,無論怎麼繼承都不能通路
    }
};
// 孫子類1 公有繼承
class CTest1 :public SONB {
public:
    void FunTest() {
        a = 200;//CB是保護繼承的,在這裡能夠繼續通路
        b = 10;
        //c = 5;父類中是私有的,無論怎麼繼承都不能通路
    }
};
// 孫子類2 公有繼承
class CTest2 :public SONC {
public:
    void FunTest() {
        //a = 200;CC是私有繼承的,在這裡就不能通路了
        //b = 10; CC是私有繼承的,在這裡就不能通路了
        //c = 5;父類中是私有的,無論怎麼繼承都不能通路
    }
};

int main() {
    SONA objA;  // 派生類A對象
    SONB objB;  // 派生類B對象
    SONC objC;  // 派生類C對象

    //公有繼承也稱之為接口繼承,父類中是公有的,
    // 子類中還有公有的,接口依然是接口。

    objA.a = 0;

    //私有繼承或者保護繼承也稱之為實作繼承**
    //使得父類中的公有成員變成了私有或者保護 
    //子類就失去了父類的接口。變成了内部實作

    //objB.a = 0;
    //objC.a = 0;

    //私有繼承和保護繼承,都是實作繼承
    //他們有什麼差別呢??
    //私有繼承下,原公有或者保護的成員,
    //都在子類中變為了私有成員,再往下繼承,就會不可通路
    //保護繼承下,原公有或者保護的成員,
    //在子類中都是保護資料,再往下繼承,還能在類内繼續通路

    return 0;
}           

複制

0x03 關于繼承中的重定義問題

當兩個類存在繼承關系時:

  1. 基類和派生類有同名成員變量或者成員函數,在派生類執行個體化對象的時候,通路到的是派生類自己的成員。
  2. 如果存在成員變量和成員函數同名,那麼隻能通路派生類的成員變量。
  3. 如果基類和派生類中存在同名的成員函數,但是參數清單不同,那麼隻能通路子類的成員函數

{% note info modern %}無論是函數名同名,還是變量名同名,還是函數和變量名同名,或是函數參數不一樣,都會發生重定義,基類中的辨別符都會被隐藏,隻能通路派生類自己的成員,如果想要使用基類中的同名成員,那麼需要使用域作用符來指定作用域。{% endnote %}

0x04 關于構造和析構函數調用順序問題

構造函數:

  • 隻有基類和派生類的時候,先調用基類的構造,再調用自己的構造
  • 隻有類成員的時候,先調用類成員的構造,再調用自己的構造
  • 既有繼承關系,又有類成員的時候,先調用基類的構造,再調用成員變量,最後調用自己的構造函數

析構函數:

析構函數的調用順序則剛好相反,如果父類或類成員隻有有參構造,那麼需要在子類的構造函數中給他們指派,使用初始化參數清單即可。

這裡是不是覺得類又很無私呢~最後構造自己,最先銷毀自己(233)