11、虛函數、多態、純虛函數
- 虛函數
- 覆蓋
- 調用
- 多態
- 實現多態的兩個必要條件
- 多態 和 this指針
- 多態的實現:虛函數表
- 虛函數表與動態綁定
- 動態綁定
- 動態綁定對性能的影響
- 純虛函數
- 抽象類
- 純抽象類
虛函數
形如class 類名{
virtual 返回值 函數名(形參表) { … }
};
的成員函數,稱為虛函數或方法
覆蓋
如果子類的成員函數和基類的虛函數具有相同的函數簽名,那么該成員函數就也是虛函數,無論其是否帶有virtual關鍵字。
與基類的虛函數構成覆蓋關系
調用
通過基類類型指針調用虛函數
- 如果基類型指針指向基類對象,則調用基類的原始版本虛函數。
- 如果基類型指針指向子類對象,則調用子類的覆蓋版本虛函數。
多態
- 如果子類提供了對基類虛函數的有效覆蓋,那么通過一個基類型指針( 指向子類對象 ),或者基類型引用( 引用子類對象 )調用該虛函數,實際被調用的將是子類中的覆蓋版本,而非基類中的原始版本,這種現象稱為多態
- 多態的重要意義在于,
- 一般情況下,調用哪個類的成員函數是由指針或引用本身的類型決定的
- 而當多態發生時,調用哪個類的成員函數是由指針或引用的實際目標對象的類型決定的
實現多態的兩個必要條件
- 需要在基類中定義虛函數,子類提供覆蓋版本
- 必須借助基類型指針 (指向子類對象) 或者基類型引用 (引用子類對象) 調用該虛函數
多態 和 this指針
調用虛函數的指針也可以是基類中的this指針,同樣能滿足多態的條件,但在構造和析構函數中除外
多態的實現:虛函數表
虛函數表與動態綁定
動態綁定
當編譯器看到通過指針或引用調用虛函數的語句時,并不急于生成有關函數跳轉的指令,相反編譯器會用一段代碼替代該語句,這段代碼在運行時才能被執行,完成如下操作
- 確定指針或引用的目標對象所占內存空間
- 從目標對象所占內存空間中找到虛表指針
- 利用虛表指針找到虛函數表
- 從虛函數表中獲取所調用虛函數的入口地址
- 根據入口地址,調用該函數
動態綁定對性能的影響
- 虛函數表本身會增加進程內存空間的開銷
- 與普通函數調用相比,虛函數調用要多出幾個步驟,會增加運行時間的開銷
- 動態綁定會妨礙編譯器通過內聯來優化代碼
- 只有在確實需要多態特性的場合才使用虛函數,否則盡量使用普通函數
純虛函數
形如class 類名{
virtual 返回值 函數名(形參表)=0;
};
的成員函數,稱為純虛函數或抽象方法
抽象類
- 擁有純虛函數的類稱為抽象類
- 抽象類不能實例化為對象
- 抽象類的子類如果不對基類中的全部純虛函數提供有效的覆蓋,那么該子類就也是抽象類
純抽象類
全部由純虛函數構成的抽象類稱為純抽象類或接口
// 純虛函數和抽象類
#include <iostream>
using namespace std;class A{
public:virtual void foo() = 0; // 純虛函數void bar(){}
};class B : public A{
public:void foo(){}
};int main(void){
// A a;
// new A;B b;new B;return 0;
}