類中的成員函數分為靜態成員函數和非靜態成員函數,而非靜態成員函數又分為普通函數和虛函數。
Q: 為什么使用虛函數
A: 使用虛函數,我們可以獲得良好的可擴展性。在一個設計比較好的面向對象程序中,大多數函數都是與基類的接口進行通信。因為使用基類接口時,調用基類接口的程序不需要改變就可以適應新類。如果用戶想添加新功能,他就可以從基類繼承並添加相應的新功能。
Q: 簡述C++虛函數作用及底層實現原理
A: 要點是要答出虛函數表和虛函數表指針的作用。
虛函數是用來實現動態綁定的。
C++中虛函數使用虛函數表和虛函數表指針實現,虛函數表是一個類的虛函數的地址表,用於索引類本身以及父類的虛函數的地址,假如子類重寫了父類的虛函數,則對應在虛函數表中會把對應的虛函數替換為子類的函數的地址(子類中可以不是虛函數,但是必須同名);虛函數表指針存在於每個對象中(通常出於效率考慮,會放在對象的開始地址處),它指向對象所在類的虛函數表的地址;在多繼承環境下,會存在多個虛函數表指針,分別指向對應不同基類的虛函數表。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
虛函數表是每個(有虛函數的)類對應一個。虛函數表指針是每個對象對應一個。
虛函數表里只能存放虛函數,不能存放普通函數。
如果一個函數不是虛函數,那么對它的調用(即該函數的地址)在編譯階段就會確定。調用虛函數的話(它的地址)要運行時才能確定。
虛函數的函數入口是動態綁定的。在運行時,程序根據基類指針指向的實際對象,來調用該對象對應版本的函數。(用該對象的虛函數表指針找到其虛函數表,進而調用不同的函數。)(只有是虛函數的情況下才會這么做(用虛函數表指針去查虛函數表)。非虛函數直接就調用自己的。)
follow up:
1. 為什么需要虛析構函數?(什么情況下要用虛析構函數?)
在存在類繼承並且析構函數中需要析構某些資源時,析構函數需要是虛函數。否則若使用父類指針指向子類對象,在delete時只會調用父類的析構函數,而不能調用子類的析構函數,造成內存泄露。
2. 一個對象訪問普通成員函數和虛函數哪個更快?
訪問普通成員函數更快,因為普通成員函數的地址在編譯階段就已確定,因此在訪問時直接調用對應地址的函數;
而虛函數在調用時,需要首先在虛函數表中尋找虛函數所在地址,因此相比普通成員函數速度要慢一些。
3. 析構函數一定是虛函數嗎?
不一定。1. 虛函數效率相對要低一些;2. 有些類並沒有子類,沒必要用虛析構函數。
4. 內聯函數、構造函數、靜態成員函數可以是虛函數嗎?
都不可以。
內聯函數(inline)需要在編譯階段展開(在編譯時就已經確定了),而虛函數是運行時動態綁定的,編譯時無法展開,因此是矛盾的;
構造函數在進行調用時還不存在父類和子類的概念,父類只會調用父類的構造函數,子類調用子類的,因此不存在動態綁定的概念(先有父類才能有子類,構造父類的時候子類還不存在,子類都還沒有怎么可能在父類里動態調用子類);
靜態成員函數(static)是以類為單位的函數,與具體對象無關,虛函數是與對象動態綁定的,因此是兩個矛盾的概念;
5. 構造函數中可以調用虛函數嗎?
可以,但是沒有意義,起不到動態綁定的效果。父類構造函數中調用的仍然是父類版本的函數,子類中調用的仍然是子類版本的函數。
6. 簡述C++中虛繼承的作用及底層實現原理?
虛繼承用於解決多繼承條件下的菱形繼承問題,底層實現原理與編譯器相關,一般通過虛基類指針實現,即各對象中只保存一份父類的對象,多繼承時通過虛基類指針引用該公共對象,從而避免菱形繼承中的二義性問題。
ref:
http://blog.csdn.net/haoel/article/details/1948051
http://songlee24.github.io/2014/09/02/cpp-virtual-table/
http://www.cuiyongjian.com/post-199.html 【分析的很有邏輯】
http://www.guokr.com/blog/469006/