C++中虛函數的做用和虛函數的工做原理

1 C++中虛函數的做用和多態

虛函數: 實現類的多態性數組

關鍵字:虛函數;虛函數的做用;多態性;多態公有繼承;動態聯編函數

C++中的虛函數的做用主要是實現了多態的機制。基類定義虛函數,子類能夠重寫該函數;在派生類中對基類定義的虛函數進行重寫時,須要在派生類中聲明該方法爲虛方法。spa

當子類從新定義了父類的虛函數後,當父類的指針指向子類對象的地址時,[即B b; A a = &b;] 父類指針根據賦給它的不一樣子類指針,動態的調用子類的該函數,而不是父類的函數(若是不使用virtual方法,請看後面★*),且這樣的函數調用發生在運行階段,而不是發生在編譯階段,稱爲動態聯編。而函數的重載能夠認爲是多態,只不過是靜態的。注意,非虛函數靜態聯編,效率要比虛函數高,可是不具有動態聯編能力。.net

★若是使用了virtual關鍵字,程序將根據引用或指針指向的 對 象 類 型 來選擇方法,不然使用引用類型或指針類型來選擇方法。指針

下面的例子解釋動態聯編性:code

 class A{ private: int i; public: A(); A(int num) :i(num) {}; virtual void fun1(); virtual void fun2(); }; class B : public A{ private: int j; public: B(int num) :j(num){}; virtual void fun2();// 重寫了基類的方法  }; // 爲方便解釋思想,省略不少代碼  A a(1); B b(2); A *a1_ptr = &a; A *a2_ptr = &b; // 當派生類「重寫」了基類的虛方法,調用該方法時 // 程序根據 指針或引用 指向的 「對象的類型」來選擇使用哪一個方法 a1_ptr->fun2();// call A::fun2(); a2_ptr->fun2();// call B::fun1(); // 不然 // 程序根據「指針或引用的類型」來選擇使用哪一個方法 a1_ptr->fun1();// call A::fun1(); a2_ptr->fun1();// call A::fun1();

2. 虛函數的底層實現機制

實現原理:虛函數表+虛表指針對象

關鍵字:虛函數底層實現機制;虛函數表;虛表指針blog

編譯器處理虛函數的方法是:爲每一個類對象添加一個隱藏成員,隱藏成員中保存了一個指向函數地址數組的指針,稱爲虛表指針(vptr),這種數組成爲虛函數表(virtual function table, vtbl),即,每一個類使用一個虛函數表,每一個類對象用一個虛表指針。繼承

舉個例子:基類對象包含一個虛表指針,指向基類中全部虛函數的地址表。派生類對象也將包含一個虛表指針,指向派生類虛函數表。看下面兩種狀況:圖片

若是派生類重寫了基類的虛方法,該派生類虛函數表將保存重寫的虛函數的地址,而不是基類的虛函數地址。

若是基類中的虛方法沒有在派生類中重寫,那麼派生類將繼承基類中的虛方法,並且派生類中虛函數表將保存基類中未被重寫的虛函數的地址。注意,若是派生類中定義了新的虛方法,則該虛函數的地址也將被添加到派生類虛函數表中。

下面的圖片體現了上述的底層實現機制:

 

C++primer第六版第十三章的虛函數的工做原理

編譯器處理虛函數的方法是:
給每一個對象添加一個指針,存放了指向虛函數表的地址,虛函數表存儲了爲類對象進行聲明的虛函數地址。好比基類對象包含一個指針,該指針指向基類全部虛函數的地址表,派生類對象將包含一個指向獨立地址表的指針,若是派生類提供了虛函數的新定義,該虛函數表將保存新函數的地址,若是派生類沒有從新定義虛函數,該虛函數表將保存函數原始版本的地址。若是派生類定義了新的虛函數,則該函數的地址將被添加到虛函數表中,注意虛函數不管多少個都只須要在對象中添加一個虛函數表的地址。

調用虛函數時,程序將查看存儲在對象中的虛函數表地址,轉向相應的虛函數表,使用類聲明中定義的第幾個虛函數,程序就使用數組的第幾個函數地址,並執行該函數。

使用虛函數後的變化:
(1) 對象將增長一個存儲地址的空間(32位系統爲4字節,64位爲8字節)。
(2) 每一個類編譯器都建立一個虛函數地址表
(3) 對每一個函數調用都須要增長在表中查找地址的操做。

虛函數的注意事項

總結前面的內容
(1) 基類方法中聲明瞭方法爲虛後,該方法在基類派生類中是虛的。
(2) 若使用指向對象的引用或指針調用虛方法,程序將根據對象類型來調用方法,而不是指針的類型。
(3)若是定義的類被用做基類,則應將那些要在派生類中從新定義的類方法聲明爲虛。
構造函數不能爲虛函數。
基類的析構函數應該爲虛函數。
友元函數不能爲虛,由於友元函數不是類成員,只有類成員才能是虛函數。
若是派生類沒有重定義函數,則會使用基類版本。
從新定義繼承的方法若和基類的方法不一樣(協變除外),會將基類方法隱藏;若是基類聲明方法被重載,則派生類也須要對重載的方法從新定義,不然調用的仍是基類的方法。

 

參考:

http://www.javashuo.com/article/p-yfujlwvp-mt.html

https://blog.csdn.net/HuYingJie_1995/article/details/88085213

相關文章
相關標籤/搜索