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


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

虛函數: 實現類的多態性

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

C++中的虛函數的作用主要是實現了多態的機制。基類定義虛函數,子類可以重寫該函數;在派生類中對基類定義的虛函數進行重寫時,需要在派生類中聲明該方法為虛方法。

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

★如果使用了virtual關鍵字,程序將根據引用或指針指向的 對 象 類 型 來選擇方法,否則使用引用類型或指針類型來選擇方法。

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

    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. 虛函數的底層實現機制

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

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

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

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

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

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

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

 

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

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

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

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

虛函數的注意事項

總結前面的內容
(1) 基類方法中聲明了方法為虛后,該方法在基類派生類中是虛的。
(2) 若使用指向對象的引用或指針調用虛方法,程序將根據對象類型來調用方法,而不是指針的類型。
(3)如果定義的類被用作基類,則應將那些要在派生類中重新定義的類方法聲明為虛。
構造函數不能為虛函數。
基類的析構函數應該為虛函數。
友元函數不能為虛,因為友元函數不是類成員,只有類成員才能是虛函數。
如果派生類沒有重定義函數,則會使用基類版本。
重新定義繼承的方法若和基類的方法不同(協變除外),會將基類方法隱藏;如果基類聲明方法被重載,則派生類也需要對重載的方法重新定義,否則調用的還是基類的方法。

 

參考:

https://blog.csdn.net/iFuMI/article/details/51088091

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM