本篇博客是作者在學習c++的過程中的筆記記錄,希望和各位讀者一起學習交流
多態
- 面向對象中的多態
根據實際的對象類型決定函數調用語句的具體調用目標
eg:p->print()
如果p指向父類對象,則執行
void print()
{
cout << “I’m Parent”;
}
如果p指向子類對象,則執行
void print()
{
cout << “I’m Child”;
} - 解決方案
- c++中支持多態
- c++中通過使用virtual關鍵字對多態進行支持
- 使用virtual聲明的函數被重寫後即可展現多態特性
- 多態成立的三個條件
- 有繼承
- 有虛函數重寫
- 用父類指針(父類引用)指向子類對象
- 多態是設計模式的基礎,多態是框架的基礎,實現多態的基礎是函數指針做函數參數
- 多態的理論基礎
- 靜態聯編和動態聯編
- 聯編是指一個程序模塊、代碼之間相互關聯的過程
- 靜態聯編是程序的匹配、連接在編譯階段實現,也稱爲早期匹配。重載函數使用靜態聯編
- 動態聯編是指程序聯編推遲到運行時進行,所以又稱爲晚期聯編(遲綁定)。switch語句和if語句是動態聯編
- 理論聯繫實際
- c++與c相同,是靜態編譯型語言
- 在編譯時,編譯器自動根據指針的類型判斷指向的是一個什麼樣的對象,所以編譯器認爲父類指針指向的是父類對象
- 由於程序沒有運行,所以不可能知道父類指針指向的具體是父類對象還是子類對象,從程序安全的角度,編譯器假設父類指針只指向父類對象,因此編譯的結果爲調用父類的成員函數。這種特性就是靜態聯編
- 多態的c++實現
virtual關鍵字,告訴編譯器這個函數要支持多態,不要根據指針類型判斷如何調用;而是要根據指針所指向的實際對象類型來判斷如何調用。冠以virtual關鍵字的函數叫虛函數;虛函數分爲兩類:一般虛函數、純虛函數 - 多態的實現效果:
同樣的調用語句有多種不同的表現形態
- 靜態聯編和動態聯編
- 虛析構函數
- 作用:虛析構函數使得在刪除指向子類對象的基類指針時可以調用子類的析構函數達到釋放子類中堆內存的目的,防止內存泄漏
- 如果父類的析構函數不加virtual關鍵字
當父類的析構函數不聲明成虛析構函數的時候,當子類繼承父類,父類的指針指向子類時,delete掉父類的指針,只調用父類的析構函數,而不調用子類的析構函數 - 如果父類的析構函數加virtual關鍵字
當父類的析構函數聲明成虛析構函數的時候,當子類繼承父類,父類的指針指向子類,delete掉父類的指針,先調用子類的析構函數,再調用父類的析構函數
- 重寫、重載、重定義
- 函數重載
必須在同一個類中
子類無法重載父類的函數,父類同名函數將被覆蓋
重載是在編譯期間根據參數類型和個數決定函數調用 - 函數重寫
必須發生在父類與子類之間
並且父類與子類中的函數必須有完全相同的原型
使用virtual關鍵字聲明之後能夠產生多態(如果不適用virtual,則叫重定義)
多態時在運行期間根據具體對象的類型決定函數調用
- 函數重載
- 多態原理探究
- 多態的實現原理
c++多態的實現原理- 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表
- 虛函數表是一個存儲類成員函數指針的數據結構
- 虛函數表是由編譯器自動生成與維護的
- virtual成員函數會被編譯器放入虛函數表中
- 存在虛函數時,每個對象中都有一個指向虛函數表的指針(vptr指針)
說明:通過虛函數表指針VPTR調用重寫函數是在程序運行時進行的,因此需要通過尋址操作才能真正確定真正應該調用的函數,而普通成員函數是在編譯的時就確定了調用的函數。在效率上,虛函數的效率要低很多。
- 證明vptr指針存在
輸出:
- 對象中的vptr指針什麼時候初始化
對象在創建時,由編譯器對vptr指針進行初始化
初始化vptr指針,初始化時分步的:- 當執行父類的構造函數的時候,vptr指向父類的虛函數表
- 當父類的構造函數執行完畢後,會把vptr指向子類的虛函數表
因此vptr的初始化是分步完成的
父類對象的vptr指向父類的虛函數表
子類對象的vptr指向子類的虛函數表
- 多態的實現原理
- 父類指針和子類指針的步長
- 鐵律:指針也是一種數據類型,c++對象的指針p++/–,仍然可用;
- 指針運算是按照指針所指的類型進行的
p++ 《==》 p=p+1 //p = (unsigned int)basep + sizeof(*p); - 父類p++與子類p++的步長不同,不能在數組上使用多態
- 步長相同的情況:子類繼承父類,但是在子類中沒有添加任何的屬性和方法
抽象類
- 純虛函數
- 純虛函數是一個在基類中說明的虛函數,在基類中沒有定義,要求任何派生類都定義自己的版本
- 純虛函數爲各派生類提供一個公共界面(接口的封裝和設計、軟件的模塊劃分)
- 語法:
virtual 類型 函數名(參數表)= 0;
- 抽象類
一個具有純虛函數的基類稱爲抽象類
抽象類不能被實例化
子類繼承了抽象類就必須實現純虛函數 - 多繼承的應用場景
c++中可以使用純虛函數來實現接口
接口類中只有函數原型定義,沒有任何數據的定義,接口類只是一個功能說明,不是功能實現
多重繼承接口不會帶來二義性和複雜性