C++_虚继承_虚函数_纯虚函数(多继承的二义性,多态)
基本信息
每一個類都有一個虛表,以及虛表指針; 虛表的內容是編譯器決定的,虛表中用于存放虛函數的指針, 程序運行時的類型信息等;
每個多態對象都存放著一個指向當前類型的虛表的指針, 該指針在構造函數中被賦值, 一般來說當調用當前這個類的構造函數, 則虛表指針就指向當前類的虛表
虛繼承
用于解決多重繼承的過程中成員訪問的二義性(菱形繼承)
格式:class 類名 : virtual 繼承方式 基類類名
注:在虛繼承的過程中,編譯器會為子類創建一個虛表,以及一個虛基表指針(占用對象空間)指向虛表(不占用對象空間)
例:
上述代碼中: B, C虛繼承于A, 他們二者”共享”一份A(用集合的方式理解,A屬于B, C的交集), 當D繼承B,C的時候只會保留一份A在D中
若未采用虛繼承在D創建對象的時候,會創建2份的A,出現冗余, 使用虛繼承繼承基類的兩個虛指針,并調整虛指針與虛基類首地址的偏移量,使得繼承過程中只保留一份的A
當使用虛繼承的過程中,虛基類被共享,無論繼承多少次,虛表指針都只會指向一份的虛基類,對象模型中只會存有一份的虛基類對象
虛函數
在基類中使用virtual修飾的成員函數,當函數聲明為虛函數時,告訴編譯器不要靜態鏈接到該函數,而是根據程序的運行過程中動態地根據該對象類型來調用函數, 就所謂的多態
注:當基類成員函數聲明為virtual, 子類進行對virtual重寫,那么重寫后的函數都為虛函數(即使該函數前面沒寫virtual關鍵字); 虛函數成員的virtual關鍵字只能出現在類中定義的函數原型前面, 不能出現在類外成員函數實現的前面
虛函數一般不聲明為inline函數, inline函數屬于靜態綁定, 而虛函數的調用是動態綁定, 如果將虛函數作為inline函數也不會出錯
構造函數, 靜態函數, 復制構造函數不可做為虛函數, 原因只在于虛函數為動態綁定;
此時,編譯器看的是指針的內容(決定了能夠調用哪些虛函數),它指向的對象類型決定了該調用誰的虛函數。因此,由于 tri 和 rec 類的對象的地址存儲在 *shape 中,所以會調用各自的 area() 函數。
動態綁定的底層實現:
虛表指針需要初始化才能調用虛函數,虛表指針在構造對象的時候初始化(初始化順序與構造函數的調用順序一致),當構造函數發現BASE具有虛函數,虛指針指向BASE的虛表中的虛函數,當執行構造子類對象的時候,子類中虛指針指向子類虛表中的虛函數;當對象創建好后,虛表指針指向的是子類的虛函數,從而對象調用虛函數時,實現多態
注:虛表會被繼承,當子類重寫虛函數的時候,那么虛表中的虛函數地址則會改變
在動態分配內存的時候,析構函數必須是虛函數(利用動態綁定)防止不會調用所需的析構函數
使用虛函數意味著多態,多態必須具備的三個條件:繼承關系; 繼承的過程中必須有同名的虛函數; 存在基類的指針或引用,通過該指針或引用調用虛函數
虛析構函數
析構函數可作為虛函數, 方便父類指針知道該調用哪個子類的析構函數(析構函數的多態)
一般情況下, 如果涉及到多態, 則將析構函數設置為virtual
//上述代碼輸出:父類析構函數,由于BASE指針偏移量的問題,未將父類析構函數設置為虛析構函數,導致靜態綁定只會釋放父類內存區,不會釋放子類內存區,導致內存泄漏;
為了避免這種錯誤應將父類,子類的析構函數設置為virtual,則會解決這個問題
注: 子類對象析構函數的調用順序,先調用子類析構函數, 然后調用父類析構函數, 調用順序與構造函數調用順序相反
純虛函數 (抽象類)
在基類重定義純虛函數,以便在派生類中重新定義該函數來適用于對象,純虛函數就相當于接口,用于規范派生類行為
包含純虛函數的類是抽象類,不能實例化, 當子類繼承抽象類時,若沒有實現純虛函數, 則子類還是抽象類
語法: virtual void function()=0;
等于0表示沒有函數體,
注: C++中,父類中的純虛函可以有實現方式, 但是編譯器會忽略, 父類依舊為抽象類, 由于抽象類不能實例化, 但是可以定義指針或引用, 通過指針和引用依舊能實現多態, 與Java中抽象類實現多態的方式一致
總結:
- 虛繼承: 虛表基表, 虛基指針主要用來記錄偏移量(虛基指針在虛基表上),以保證多繼承的過程中只復制一份的基類
- 虛函數: 虛表,虛指針主要用于指向虛函數(虛指針指向虛表中的虛函數地址)
- 純虛函數: 就是接口, 規范子類行為
總結
以上是生活随笔為你收集整理的C++_虚继承_虚函数_纯虚函数(多继承的二义性,多态)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python写xml文件_用python
- 下一篇: github page hexo博客gi