前言
大家好吖,歡迎來到 YY 滴C++系列 ,熱烈歡迎! 本章主要內容面向接觸過C++的老鐵
主要內容含:
歡迎訂閱 YY滴C++專欄!更多干貨持續更新!以下是傳送門!
目錄
- 一.多態的概念
- 二.多態的實現
- 1)虛函數&虛函數表
- 2)虛函數的重寫(覆蓋)
- 3)多態的構成條件
- 4)虛函數重寫的兩種特殊情況:
- 【1】協變:(基類與派生類虛函數返回值類型不同)
- 【2】析構函數的重寫:(基類與派生類析構函數的名字不同)
- 三.【override】【final】關鍵字——幫助用戶檢測是否重寫(C++11)
- 【1】 final:表示虛函數不能被重寫,被重寫即報錯
- 【2】override:檢查虛函數是否重寫了別的虛函數,重寫了即報錯
- 四. 多態的具體應用:抽象類(接口類)(純虛函數類)
- 1)利用 [ 只有重寫純虛函數 派生類才能實例化出對象 ] 性質
- 2)實現繼承與接口繼承
一.多態的概念
- 多態是在不同繼承關系的類對象,去調用 同一 函數,產生了 不同 的行為。比如Student繼承了Person。Person對象買票全價,Student對象買票半價。
- 例:iphone和安卓手機用戶打車同程不同價
二.多態的實現
1)虛函數&虛函數表
- 虛函數:即被 virtual 修飾的類成員函數稱為虛函數。
class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl;}
};
- 虛函數表本質是一個存虛函數指針 的 指針數組,一般情況這個數組最后面放了一個nullptr。
- 虛函數表:虛函數表存的是虛函數指針,不是虛函數,虛函數和普通函數一樣的,都是存在代碼段的,只是他的指針又存到了虛函數表中。
- 一個含有虛函數的類中都至少都有一個虛函數表指針,因為虛函數的地址要被放到虛函數表中,虛函數表也簡稱虛表
2)虛函數的重寫(覆蓋)
- 虛函數的重寫(覆蓋): 派生類中有一個跟基類完全相同的虛函數 (即派生類虛函數與基類虛函數的返回值類型、函數名字、參數列表完全相同) ,稱子類的虛函數 重寫 了基類的虛函數。
3)多態的構成條件
- 必須通過 基類的指針 來 引用 調用虛函數
- 被調用的函數 必須是虛函數,且 派生類必須對基類的虛函數進行重寫
//多態條件2:被調用的函數 必須是虛函數
class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public: //多態條件2:派生類必須對基類的虛函數進行重寫virtual void BuyTicket() { cout << "買票-半價" << endl; }
/*注意:在重寫基類虛函數時,派生類的虛函數在不加virtual關鍵字時,雖然也可以構成重寫(因
為繼承后基類的虛函數被繼承下來了在派生類依舊保持虛函數屬性),但是該種寫法不是很規范,不建議
這樣使用*/
/*void BuyTicket() { cout << "買票-半價" << endl; }*/
};void Func(Person& p) //多態條件1:必須通過基類的指針來“引用”調用虛函數
{
p.BuyTicket();
}int main()
{
Person ps;
Student st;
Func(ps);
Func(st);return 0;
}
4)虛函數重寫的兩種特殊情況:
【1】協變:(基類與派生類虛函數返回值類型不同)
- 派生類重寫基類虛函數時 ,與基類虛函數 返回值類型不同 。即如下代碼所示:【基類虛函數返回基類對象的指針或者引用,派生類虛函數返回派生類對象的指針或者引用時】,稱為協變
class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
【2】析構函數的重寫:(基類與派生類析構函數的名字不同)
- 如果 基類的析構函數為虛函數 ,此時派生類析構函數只要定義, 無論是否加virtual關鍵字 ,
都與基類的析構函數構成重寫,雖然基類與派生類析構函數名字不同。 雖然函數名不相同【~Person() 】 【~Student() 】,看起來違背了重寫的規則,其實不然,這里可以理解為編譯器對析構函數的名稱做了特殊處理,編譯后析構函數的名稱統一處理成 destructor 。
class Person {
public: //基類的析構函數為虛函數virtual ~Person() {cout << "~Person()" << endl;}
};class Student : public Person {
public://~Student() { cout << "~Student()" << endl; 不加virtual也行virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生類Student的析構函數重寫了Person的析構函數,下面的delete對象調用析構函數,
//才能構成多態,才能保證p1和p2指向的對象正確的調用析構函數。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}
三.【override】【final】關鍵字——幫助用戶檢測是否重寫(C++11)
- 從上面可以看出,C++對函數重寫的要求比較嚴格,但是有些情況下由于疏忽,可能會導致函數
名字母次序寫反而無法構成重載,而這種錯誤在編譯期間是不會報出的,只有在程序運行時沒有
得到預期結果才來debug會得不償失,因此:C++11從兩個角度提供了 override 和 final 兩個關鍵字,可以幫
助用戶檢測是否重寫。- final:表示虛函數不能被重寫,被重寫即報錯
- override:檢查虛函數是否重寫了別的虛函數,重寫了即報錯
【1】 final:表示虛函數不能被重寫,被重寫即報錯
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒適" << endl;}
};
【2】override:檢查虛函數是否重寫了別的虛函數,重寫了即報錯
class Car{
public:virtual void Drive(){}
};
class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒適" << endl;}
};
四. 多態的具體應用:抽象類(接口類)(純虛函數類)
1)利用 [ 只有重寫純虛函數 派生類才能實例化出對象 ] 性質
- 在虛函數的后面寫上 =0 ,則這個函數為 純虛函數 。 包含純虛函數的類 叫做 抽象類(也叫接口類) , [ 抽象類不能實例化出對象 ]&[ 派生類繼承后也不能實例化出對象 ] 。只有 [ 重寫純虛函數 ] ,派生類才能實例化出對象。純虛函數規范了派生類必須重寫,另外純虛函數更體現出了接口繼承。
class Car
{
public:
virtual void Drive() = 0; //在虛函數的后面寫上 =0,這個函數為 純虛函數
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒適" << endl; //只有 [ 重寫純虛函數 ] ,派生類才能實例化出對象}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}
2)實現繼承與接口繼承
- 普通函數的繼承是一種 實現繼承 ,派生類繼承了基類函數,可以使用函數,繼承的是函數的實現。
- 虛函數的繼承是一種 接口繼承 ,派生類繼承的是基類虛函數的接口, 目的是為了重寫,達成多態 ,繼承的是接口。所以如果不實現多態,不要把函數定義成虛函數。