C++之虚函数
都說面向對象的三大特性是封裝、繼承、多態。C++作為一門面向對象編程語言,肯定也是具備了面向對象的三大特性,那么在C++中是如何實現多態的呢?
在C++中是通過虛函數動態綁定的方式實現多態的。
虛函數與純虛函數
首先我們來回顧一下虛函數,在C++中是使用virtual關鍵字修飾的函數就是虛函數,下面是一個簡單的虛函數例子:
class Base{ public:// 虛函數,必須實現,否則編譯報錯virtual void f1() const{std::cout << "我是Base的f1函數" << std::endl;} };class A:public Base{void f1() const override{std::cout << "我是A的f1函數" << std::endl;} };虛函數必須在基類中實現,如果不實現的話就會編譯報錯。
如果我們不想實現虛函數的話可以將其聲明為純虛函數,純虛函數的聲明方式如下:
virtual 返回值類型 函數名(函數參數) = 0;聲明了純虛函數的類稱為抽象類,繼承抽象類的最終子類必須父類的純虛函數,否則不能生產對應的類對象。以下是一個純虛函數的例子:
class Base{ public:// 虛函數,必須實現,否則編譯報錯virtual void f1() const{std::cout << "我是Base的f1函數" << std::endl;}// 純虛函數,不需要實現virtual void f2() const = 0; };class A:public Base{ public:void f1() const override{std::cout << "我是A的f1函數" << std::endl;}void f2() const override{std::cout << "我是A的純虛函數f2" << std::endl;} };有了虛函數我們就能通過基類的的指針進行動態綁定,在運行時訪問到子類的函數,但是動態綁定只能發生在指針或引用上。例如在以下的例子中,函數test3是不會訪問到子類的函數的,
它訪問的函數依然是基類的虛函數,也就是說它沒有發生動態綁定,因為它既不是指針也不是引用。
運行打印結果:
我是A的f1函數 我是A的f1函數 我是Base的f1函數如果一個類可能會被繼承,這個類的析構函數應該被聲明為一個虛函數,否則會引發內存泄漏。例如以下例子:
class Base{ public:// 虛函數,必須實現,否則編譯報錯virtual void f1() const{std::cout << "我是Base的f1函數" << std::endl;}~Base(){std::cout << "Base的析構函數" << std::endl;} };class A:public Base{ public:void f1() const override{std::cout << "我是A的f1函數" << std::endl;}~A(){std::cout << "A的析構函數" << std::endl;} };int main(int arg,char** argv) {Base *base = new A();delete base;return 0; }運行輸出如下:
Base的析構函數從運行結果可以看出A沒有被正確析構,這是因為它的基類Base的析構函數沒有被聲明為虛函數的原因,此時只要我們把Base類的析構函數聲明為虛函數即可修復這個內存泄漏的問題,也就是:
class Base{ public:// 虛函數,必須實現,否則編譯報錯virtual void f1() const{std::cout << "我是Base的f1函數" << std::endl;}// 可能被繼承的類的析構函數應該是一個虛函數virtual ~Base(){std::cout << "Base的析構函數" << std::endl;} };虛函數總結:
1、當我們在派生類中覆蓋了某個虛函數時,可以再一次使用virtual關鍵字指出該函數的性質。然而這么做并非必須,因為一旦某個函數被聲明成虛函數,則在所有派生類中它都是虛函數。
2、虛函數只有在引用或者指針調用時才會發生動態綁定;
3、基類的析構函數需要聲明為虛函數;
4、虛函數必須要在基類實現,不實現,編譯會報錯;
5、如果子類沒有實現父類的純虛函數,則該子類不能被構造成一個對象。
多態實現原理-虛函數表
通過上面的例子我們知道了在C++中通過引用或指針的形式進行虛函數的動態綁定而實現多態,那么動態綁定在C++中是如何實現呢?答案是虛函數表。
所謂的虛函數表就是:
當編譯器在編譯過程中遇到virtual關鍵字時,它不會對函數調用進行綁定,而是為包含虛函數的類建立一張虛函數表Vtable。在虛函數表中,編譯器按照虛函數的聲明順序依次保存虛函數地址。同時,編譯器會在類中添加一個隱藏的虛函數指針VPTR,指向虛函數表。在創建對象時,將虛函數指針VPTR放置在對象的起始位置,為其分配空間,并調用構造函數將其初始化為虛函數表地址。需要注意的是,虛函數表不占用對象空間。
虛函數表總結:
1、單繼承下的虛函數表
虛函數表中的指針順序,按照虛函數聲明的順序排序;基類的虛函數指針在派生類的前面。
2、多繼承下的虛函數表
多繼承關系下會有多個虛函數表,也會有多個指向不同虛函數表的指針;
推薦閱讀
C++之指針掃盲
C++之智能指針
C++之指針與引用
C++之右值引用
C++之多線程一
C++之多線程二
C++之異常處理
關注我,一起進步,人生不止coding!!!
總結
- 上一篇: 推荐书籍android开发
- 下一篇: C++之菱形继承与虚继承(含虚函数)