摘要:當子類繼承了父類并且子類重寫了父類的虛函數之后,我們可以看到此時子類中虛函數指針對應的虛函數表中存的是子類經過重寫的函數了。
前言:相信小伙伴們在學習到C++面向對象特性之一的多態的時候,都或多或少有一些疑惑。搞不清楚多態在底層是如何實現的,今天我就帶大家刨析一下多態的底層實現,了解一下虛函數指針和虛函數表到底是什么東西?(注意本文操作環境是VS2019 x86架構 32位機器)
1.1.1 定義:
多態按字面的意思就是多種形態。當類之間存在層次結構,并且類之間是通過繼承關聯時,就會用到多態。
C++ 多態意味著調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。
----------來自菜鳥教程
1.2.1 多態分為兩類:
·靜態多態:函數重載和運算符重載屬于靜態重載,復用函數名
·動態多態:派生類和虛函數實現運行時多態
1.2.2 靜態多態和動態多態的區別:
·靜態多態的函數地址早綁定–編譯階段確定函數地址
·動態多態的函數地址晚綁定–運行階段確定函數地址
2.1 靜態多態代碼:
#include using namespace std;class Father{public: void speak() { cout << "爸爸在說話!" << endl; }};class Son :public Father{public: void speak() { cout << "兒子在說話!" << endl; }};//執行說話函數//地址早被綁定 在編譯階段確定函數地址void doSpeak(Father &father)//父類引用接收子類對象{ father.speak();}void test01(){ Son son; doSpeak(son);}int main(){ test01(); return 0;}
2.2 運行結果:
2.3 分析:
在此案例中,派生類和基類中都出現了speak函數,當用父類指針或者引用接收子類對象時,程序會執行基類中的同名函數,這是為什么呢?因為父類中的speak函數地址在編譯期間就被綁定,所以在執行程序時無論傳遞的是哪種對象,執行的都是基類中的speak函數。這就是靜態動態的弊端,那么如果想實現傳入哪種對象就執行哪種類的函數,這就需要用到動態多態了。
動態多態滿足條件:
1、有繼承關系
2、子類重寫父類的虛函數
動態多態的使用:
父類的指針或者引用指向子類對象
3.1.1動態多態代碼
#include using namespace std;class Father{public: virtual void speak() { cout << "爸爸在說話!" << endl; }};class Son :public Father{public: void speak() { cout << "兒子在說話!" << endl; }};//執行說話函數//地址早被綁定 在編譯階段確定函數地址void doSpeak(Father &father)//父類引用接收子類對象{ father.speak();}void test01(){ Son son; doSpeak(son);}int main(){ test01(); return 0;}
3.1.2 運行結果:
3.1.3 分析:
靜態多態變為動態多態只需要給父類的函數加上virtual關鍵字變為虛函數。
小知識:在C++中空類也占內存,占一個字節的空間
3.2.1
我們先來看一下父類的函數前加上virtual關鍵字,父類的內存占用有什么變化?
通過運行發現此時父類所占的空間變成了4個字節,那么這四個字節到底是存了什么????其實聰明的小伙伴們可能已經猜出來這四個字節是什么了,沒錯存的就是一個指針。這個指針就叫虛函數指針,簡寫為vfptr(virtual function pointer). 對于一個類來說,如果類中存在虛函數,那么編譯器就會自動在類中加一條生成虛函數指針的語句(void * vfptr),并且在類的構造函數中為虛函數指針進行賦值(vfptr=&Father::vtal),此時這個虛函數指針就會指向虛函數表。所以,如果對象存在虛函數,那么編譯器就會生成一個指向虛函數表的指針,所有的虛函數都存在于這個表中,虛函數表就可以理解為一個數組,每個單元用來存放虛函數的地址。
3.2.2
此時我們在父類中寫兩個虛函數
然后打開vs2019的調試功能查看一下對象father可以看到
此時可以發現父類對象確實有一個vfptr指針,這個指針對應的
表里就是儲存著兩個虛函數的地址,這兩個函數都是屬于父類的。
3.2.3
當子類繼承了父類并且子類重寫了父類的虛函數之后,我們可以看到:
此時子類中虛函數指針對應的虛函數表中存的是子類經過重寫的函數了。所以當傳入一個子類對象時通過查詢子類vfptr找到對應的虛函數表,從而找到其中存的函數地址去執行,這也就是為什么動態多態可以根據傳入對象的不同來執行不同的語句。
3.2.4 畫圖演示
由此證明,我們的結論是正確的!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/119812.html
摘要:也就是說,一個實例變量,在的對象初始化過程中,最多可以被初始化次。當所有必要的類都已經裝載結束,開始執行方法體,并用創建對象。對子類成員數據按照它們聲明的順序初始化,執行子類構造函數的其余部分。 類的拷貝和構造 C++是默認具有拷貝語義的,對于沒有拷貝運算符和拷貝構造函數的類,可以直接進行二進制拷貝,但是Java并不天生支持深拷貝,它的拷貝只是拷貝在堆上的地址,不同的變量引用的是堆上的...
摘要:繼承方式繼承方式限定了基類成員在派生類中的訪問權限,包括公有的私有的和受保護的。所以子類給父類引用賦值也是可以的,相當于給子類對象中繼承的父類部分起了別名。如圖成員函數也是如此,當子類與父類具有函數名相同的函數時,還是符合就近原則。 ...
摘要:博主在公眾號后臺設置了關鍵字回復,回復下面的里面的內容,可免費獲得學習視頻和資料。 博主在公眾號后臺設置了關鍵字回復, 回復下面的【】里面的內容, 可免費獲得C++學習視頻和資料。 如回復:C++基礎 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 【C++】 【1】...
閱讀 2349·2021-11-15 11:38
閱讀 3561·2021-09-22 15:16
閱讀 1202·2021-09-10 11:11
閱讀 3173·2021-09-10 10:51
閱讀 2956·2019-08-30 15:56
閱讀 2791·2019-08-30 15:44
閱讀 3195·2019-08-28 18:28
閱讀 3535·2019-08-26 13:36