天天看點

多重繼承_C++ 繼承和封裝

這一篇介紹一下 C++面向對象另外兩個特性繼承和封裝

如有侵權,請聯系删除,如有錯誤,歡迎大家指正,謝謝

繼承

  • 繼承(是 is-a 的關系)的作用是提高代碼的重用性,子類繼承了父類,子類可以調用父類的成員,跟調用自己的成員一樣,父類被繼承後沒有影響,還是可以正常定義對象
繼承的限定詞
  • private繼承,父類中 public 和 protected 成員,在子類中為 private,父類中 private 成員在子類中不變
  • protected繼承,父類中 public 成員,在子類中為 protected,父類中 protected 和 private 成員在子類中不變
  • public,父類中如何子類中如何
  • 繼承限定詞是将父類中通路權限大于等于限定詞降為和限定詞權限相同,需要說明一點,子類 private 或 protected 繼承父類,子類中的父類成員權限發生變化,父類中的沒有變化,還是可以正常定義對象
  • 如果不寫繼承限定詞,預設是 private 繼承的
成員的繼承
  • 友元函數不能被繼承,靜态成員隻有一份,子類繼承父類,父類有靜态成員(靜态函數/靜态成員),所有的子類和父類共享一份靜态成員
  • 無參構造函數,從上向下調用,先調用父類再調用子類,如果父類還有父類,那就先調用父類的父類的構造函數,以此類推,或者說從輩分高到輩分低
class TestA {
public:
    TestA() {
        cout << "TestA ctor" << endl;
    }

    TestA(int i) {
        cout << "TestA param ctor" << endl;
    }
};

class Test : public TestA {
public:
    Test() { // 此處預設是 Test() : TestA()
        cout << "Test ctor" << endl;
    }
};

Test();
// 運作結果:
// TestA ctor
// Test ctor
           
  • 有參構造函數,如果父類的構造函數有參數子類的構造函數必須顯式調用父類的構造函數,子類隻關心自己的父類,父類的父類有幾個參數對子類沒有影響,父類的父類的構造函數需要在父類的構造函數的初始化清單中顯式調用并傳遞參數,父類的構造函數可以重載,編譯器會根據子類的初始化清單調用相應的父類的構造函數,預設的情況下,子類構造函數的參數清單是調用無參的父類構造函數
class TestA {
public:
    TestA() {
        cout << "TestA ctor" << endl;
    }

    TestA(int i) {
        cout << "TestA param ctor" << endl;
    }
};

class Test : public TestA {
public:
    Test() : TestA(10){  // Test(int n) : TestA(n) 建立對象時就要傳參
        cout << "Test ctor" << endl;
    }
};

Test();
// 運作結果:
// TestA param ctor
// Test ctor
           
  • 析構函數,析構函數的調用順序與構造函數的調用順序相反,先調用子類的析構函數再調用父類的析構函數,即從輩分低到輩分高
class TestA {
public:
    ~TestA() {
        cout << "TestA dtor" << endl;
    }
};

class Test : public TestA {
public:
    ~Test() {
        cout << "Test dtor" << endl;
    }
};

Test();
// 運作結果:
// Test dtor
// TestA dtor
           
覆寫
  • 父類和子類中出現同名的成員(成員變量和成員函數)時,C++采用的一種處理方式叫覆寫
  • 資料成員,在類内,子類覆寫父類,預設是調用子類的成員,要調用父類的成員需要用類名作用域,在類外也是一樣的,預設是調用子類的成員,要調用父類的成員需要用類名作用域
class TestA {
public:
    int n1;
    TestA() : n1(1), n2(2) { }

private:
    int n2;
};

class Test : public TestA {
public:
    int n1;
    Test() : n1(3), n2(4) { }
    void func() {
        cout << n1 << " " << n2 << " " << TestA::n1 << endl;
    }
private:
    int n2;
};

Test().func();
cout << Test().n1 << " " << Test().TestA::n1 << endl;

// 運作結果:
// 3 4 1
// 3 1
           
  • 函數成員,函數成員的覆寫通常情況下與資料成員一樣,成員函數的覆寫隻會看函數名是否是一樣的,不看傳回值類型和參數清單,是以父類和子類同名函數參數清單不同,不存在重載關系
class TestA {
public:
    void func() {
        cout << "TestA func" << endl;
    }
};

class Test : public TestA {
public:
    void func() {
        cout << "Test func" << endl;
    }
};

Test().func();
Test().TestA::func();

// 運作結果:
// Test func
// TestA func
           
虛繼承
  • 虛繼承是多重繼承中特有的概念,虛基類是為解決多重繼承中通路不明确的問題而出現的
// ====== 這是一個菱形繼承的測試用例 ======
class A {
public:
    int n;
};

class B : virtual public A { };

class C : virtual public A { };

class Test : public B, C { 
public:
    void func() {
        cout << n << endl;
    }
};
           
  • 上面的代碼中,若沒有 virtual 修飾B類和C類的繼承關系B類和C類就會都複制一份A類中的n,Test的對象調用n,就會發生通路不明确的現象,這是因為Test繼承的B類和C類中,都有變量 n,加上 virtual 修飾後,B類和C類不會在類中複制一份n,隻是繼承了A類中n的使用權,是以還是隻有一份變量n,另外還需要注意Test定義的不同對象中的n之間是互相獨立的

封裝

  • 封裝是 C++ 面向對象特征之一
  • 封裝是将客觀事物抽象得到的屬性(資料)和方法(函數)相結合,形成一個整體,即類,其中資料和函數都是類的成員
  • 封裝可以隐藏細節,讓代碼子產品化,隻留下接口給外界調用
  • 實作代碼重用
如果未特殊說明,以上測試均是在win10 vs2017 64bit編譯器下進行的