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 {
};