1. 程式人生 > >C++面向物件部分

C++面向物件部分

基類和派生類

基類中使用virtual宣告函式,顯示的說明這是可以進行過載的。子類中在函式的最後新增上override關鍵字顯示的說明過載基類的函式,這是可選的。

但是,如果不宣告為虛擬函式,則解析發生在編譯時,而不是執行時。這樣會導致在宣告為基類的引用或者指標時,只能呼叫基類的函式,即使此處使用了派生類,也無法進行執行時檢驗!virtual只需要在基類中宣告一次即可,這樣整個繼承鏈中的子類都可以不用宣告,而在執行時動態檢驗。

執行時繫結: 基類的引用或者指標可以隱式地轉化成派生類的,這樣程式在執行時可以選擇具體的功能,這是執行時繫結。

程式碼例項:

#include <iostream>
using namespace std; class A { public: virtual ~A() {} virtual void print() { cout << "I am base class" << endl; } }; class SubA: public A { public: void print() override { cout << "I am sub class" << endl; } }; void test(A &
a) { a.print(); } int main() { A a; SubA sa; test(a); test(sa); return 0; } /* 輸出結果: I am base class I am sub class */

基類都應該有虛解構函式,即使無任何操作也應該如此。

上述程式碼如果去掉基類中的virtual關鍵字和子類中的override關鍵字,則輸出:

I am base class
I am base class

因為此時是在編譯期間執行的,而不是執行時檢查!!!

如果基類定義了靜態成員,則整個繼承體系中只有唯一的一個靜態成員。不論基類中派生出多少個子類。。但是靜態成員除了這個特性之外,其餘的訪問特性都遵循繼承的訪問原則。

在定義基類的時候,新增final關鍵字表示該基類不能被繼承:

class A final {/*  */};  // 不能被繼承

型別轉換的時候,基類可以轉換成派生類,但是反過來不行!

虛擬函式

前面說過,只有宣告為virtual的函式才能執行動態繫結。否則永遠都是執行基類的函式,只能在編譯期間確定。一旦函式在基類中宣告為虛擬函式,則在所有的子類中都是虛擬函式。

子類與基類同名但是引數不同的函式不是重寫,因此為了保證正確性,最好是在子類要重寫的函式聲明後新增override關鍵字。

虛擬函式也有預設實參,而且子類的預設實參可以和基類的預設實參不同,這可以在執行時確定。但是父子最好一致。

抽象基類

抽象基類看成是C++的介面機制,通過函式後新增=0實現。一般來說,為了實現動態繫結,我們需要把純虛擬函式宣告為virtual型別的。

#include <iostream>
using namespace std;

class Interface {
  public:
    // 純虛擬函式
    virtual void test() = 0; 
};

class A: public Interface {
  public:
    void test() override {
        cout << "ok" << endl;
    }
};

// 如要執行動態繫結,純虛擬函式必須宣告為virtual
void test(Interface &it) {
    it.test();
}

int main() {
    A a;
    test(a);
    return 0;
}

訪問控制與繼承

訪問屬性的圖表

友元的關係不能被繼承!!

class預設私有繼承,struct預設公有繼承。

派生類向基類轉換的可訪問性:

  • 只有D公有地繼承B時,使用者程式碼才能使用派生類向基類轉換。
  • 不論D以什麼方式繼承B,D的成員函式和友元都能使用派生類向基類的轉換
  • 如果D繼承B的方式是公有的或者受保護的,則D的派生類的成員和友元可以使用D向B的型別轉換;如果是私有繼承,則不行

繼承中的類的作用域

繼承關係中,子類的作用域巢狀在基類的作用域中。當一個名字在派生類作用域中無法解析的時候,會向基類作用域中查詢。

前面提到過,如果基類的成員函式不宣告為virtual,則無法執行動態繫結,也就是使用指標或者引用的時候,都是在編譯期進行的,只能使用基類的;如果宣告為virtual,則是在執行期根據指向或者引用的物件來動態決定的;注意到,如果是值傳遞,則無法執行動態繫結,仍然呼叫基類的。

如果派生類中成員與基類成員同名,則派生類成員會掩蓋基類成員,一般在程式設計的時候,最好不要掩蓋基類成員的非virtual成員。如果要呼叫基類成員,需要使用域作用符號。程式碼例項:

#include <iostream>
using namespace std;

class Base {
  public:
    virtual void print() {
        cout << "I am Base" << endl;
    }
};

class Base1 {
  public:
    virtual void print() {
        cout << "I am Base 1" << endl;
    }
};

class Derived: public Base, public Base1 {
  public:
    void print() override {
        cout << "I am Derived" << endl;
    }
};


int main() {
    Derived d;
    d.print();
    d.Base::print();
    d.Base1::print();
    Base* b = &d;  // 動態繫結
    b->print();
    Base1 &b1 = d; // 動態繫結
    b1.print();
    // 下面是錯誤的,多重繼承沒法轉化
    // Base b = d;
    // Base1 b = d;
    return 0;
}
/*
輸出結果:
I am Derived
I am Base
I am Base 1
I am Derived
I am Derived
*/

上述程式碼的動態繫結同樣根據實際情況進行轉換,不過無法執行值傳遞的轉換了。

建構函式和拷貝建構函式

基類的解構函式需要宣告為virtual的,否則在動態繫結後進行析構時,會發生未定義的錯誤。

基類建構函式先執行,然後再執行派生類的建構函式;派生類的解構函式先執行,然後再執行基類的建構函式。

容器與繼承

使用容器存放繼承體系的時候,一般使用簡介存放的方式,因為不允許在容器中儲存不同型別的元素。我們不能把具有繼承關係的多種型別的物件直接存放在容器當中。實際中一般使用存放物件(智慧)指標的形式來解決問題。

實際例子:

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class Base {
  public:
    virtual void print() {
        cout << "I am Base" << endl;
    }
};

class Derived: public Base {
  public:
    void print() override {
        cout << "I am Derived" << endl;
    }
};


int main() {
    vector<shared_ptr<Base>>v;
    v.push_back(make_shared<Derived>());
    v.push_back(make_shared<Base>());
    v[0]->print();
    v[1]->print();
    return 0;
}
/*
輸出結果:
I am Derived
I am Base
*/

多重繼承與虛繼承

派生類的建構函式的初始值列表將實參分別傳遞給每個直接基類。其中基類的構造順序與派生列表中基類出現的順序一致,但是派生類建構函式初始值列表中基類的順序無關。

派生類的解構函式之負責清理本身分配的資源,派生類的成員以及基類都是自動銷燬的。

基類與派生的動態繫結關係在"繼承中的類的作用域"一節中介紹,不在贅述。

多重繼承時,一個派生類可能會繼承多個基類,使用多重繼承的方式可以保證基類只出現一次。在繼承前面新增關鍵字virtual即可。

class Base {
  public:
    virtual void print() {
        cout << "I am Base" << endl;
    }
};

class Derived1: virtual public Base {
  public:
    void print() override {
        cout << "I am Derived" << endl;
    }
};

class Derived2: virtual public Base {
  public:
    void print() override {
        cout << "I am Derived" << endl;
    }
};

class FinalClass: public Derived1, public Derived2 {
};