C语言面向对象编程(三):虚函数与多态
? ? 我們在這里提供一個 C 中的實現,模仿 VTABLE 這種機制,但一切都需要我們自己在代碼中裝配。
? ? 之前在網上看到一篇描述 C 語言實現虛函數和多態的文章,談到在基類中保存派生類的指針、在派生類中保存基類的指針來實現相互調用,保障基類、派生類在使用虛函數時的行為和 C++ 類似。我覺得這種方法有很大的局限性,不說繼承層次的問題,單單是在基類中保存派生類指針這一做法,就已經違反了虛函數和多態的本意——多態就是要通過基類接口來使用派生類,如果基類還需要知道派生類的信息……。
? ? 我的基本思路是:
- 在“基類”中顯式聲明一個 void** 成員,作為數組保存基類定義的所有函數指針,同時聲明一個 int 類型的成員,指明 void* 數組的長度。
- “基類”定義的每個函數指針在數組中的位置、順序是固定的,這是約定,必須的
- 每個“派生類”都必須填充基類的函數指針數組(可能要動態增長),沒有重寫虛函數時,對應位置置 0
- “基類”的函數實現中,遍歷函數指針數組,找到繼承層次中的最后一個非 0 的函數指針,就是實際應該調用的和對象相對應的函數實現
? ? 好了,先來看一點代碼:
[cpp] view plaincopy print?? ? 注意,derived 和 derived_2 并沒有定義 func_1 和 func_2 。在 C 的虛函數實現中,如果派生類要重寫虛函數,不需要在派生類中顯式聲明。要做的是,在實現文件中實現你要重寫的函數,在構造函數中把重寫的函數填入虛函數表。
? ? 我們面臨一個問題,派生類不知道基類的函數實現在什么地方(從高內聚、低耦合的原則來看),在構造派生類實例時,如何初始化虛函數表?在 C++ 中編譯器會自動調用繼承層次上所有父(祖先)類的構造函數,也可以顯式在派生類的構造函數的初始化列表中調用基類的構造函數。怎么辦?
? ? 我們提供一個不那么優雅的解決辦法:
? ? 每個類在實現時,都提供兩個函數,一個構造函數,一個初始化函數,前者用戶生成一個類,后者用于繼承層次緊接自己的類來調用以便正確初始化虛函數表。依據這樣的原則,一個派生類,只需要調用直接基類的初始化函數即可,每個派生類都保證這一點,一切都可以進行下去。
? ? 下面是要實現的兩個函數:
[cpp] view plaincopy print?? ? 說完了構造函數,對應的要說析構函數,而且析構函數要是虛函數。在刪除一個對象時,需要從派生類的析構函數依次調用到繼承層次最頂層的基類的析構函數。這點在 C 中也是可以保障的。做法是:給基類顯式聲明一個析構函數,基類的實現中查找虛函數表,從后往前調用即可。函數聲明如下:
[cpp] view plaincopy print?? ? 說完構造、析構,該說這里的虛函數表到底是怎么回事了。我們先畫個圖,還是以剛才的 base 、 derived 、derived_2 為例來說明,一看圖就明白了:
? ? 我們假定 derived 類實現了三個虛函數, derived_2 類實現了兩個,func_2 沒有實現,上圖就是 derived_2 的實例所擁有的最終的虛函數表,表的長度( vt_size )是 9?。如果是 derived 的實例,就沒有表中的最后三項,表的長度( vt_size )是 6 。
? ? 必須限制的是:基類必須實現所有的虛函數,只有這樣,這套實現機制才可以運轉下去。因為一切的發生是從基類的實現函數進入,通過遍歷虛函數表來找到派生類的實現函數的。
? ? 當我們通過 base 類型的指針(實際指向 derived_2 的實例)來訪問 func_1 時,基類實現的 func_1 會找到 VTABLE 中的 derived_2_func_1 進行調用。
??
? ? 好啦,到現在為止,基本說明白了實現原理,至于 初始化函數如何裝配虛函數表、基類的虛函數實現,可以根據上面的思路寫出代碼來。按照我的這種方法實現的虛函數,通過基類指針訪問,行為基本和 C++ 一致。
總結
以上是生活随笔為你收集整理的C语言面向对象编程(三):虚函数与多态的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html5 中 video 标签,H5页
- 下一篇: php多个参数绑定,php – 如何绑定