一、什么是虛函數、純虛函數、抽象基類
虛函數:在某基類中聲明為 virtual 並在一個或多個派生類中被重新定 義的成員函數。
純虛函數:是一種特殊的虛函數,使用virtual關鍵字,並且在其后面加上=0。
抽象基類:在基類中加入至少一個純虛函數,使基類成為抽象類。
二、為什么要使用虛函數
在理解這個問題前,就必須要理解什么是晚捆綁。
晚捆綁是相對於早捆綁而言的,那么什么又是捆綁呢?把函數體與函數調用相聯系稱為捆綁,當捆綁在程序運行之前完成時,這稱為早捆綁。那么當捆綁根據對象的類型,發生在運行時,就稱為晚捆綁。
而使用晚捆綁,無需檢查對象的類型,只需要檢查對象是否支持特性和方法即可。
為了引發晚捆綁,C++要求在基類中聲明這個函數時使用virture關鍵字。晚捆綁只對virtual函數起作用,而且只在使用含有virtual函數的基類的地址時發生。
三、關於重寫
如果一個函數在基類中被聲明為virtual,那么在所有的派生類中它都是virtual,在派生類中virtual函數的重定義通常稱為重寫。
四、在C++中如何實現晚捆綁
虛函數主要有兩個步驟:
1、每一個類產生出一堆指向虛函數的指針,放在表格中。這個表格被稱為virtual table(vtbl)
2、每一個類對象被安插一個指針,指向相關的virtual table,通常這個指針被稱為vptr
結構圖如下:

每當創建一個包含有虛函數的類或從包含有虛函數的類派生一個類時,編譯器就為這個類創建一個唯一的vtbl。如果在這個派生類中沒有對在基類中聲明為virtual的函數進行重新定義,編譯器就使用基類的這個虛函數地址。然后編譯器在這個類中放置vptr。當使用簡單繼承時,對於每個對象都只有一個vtbl。vptr必須被初始化為指向相應的vtbl的起始地址。
五、虛函數的存放類型信息
假如沒有虛函數,那么對象的長度就是所期望的長度:比如當個int的長度。而帶有單個虛函數的One Virtual,對象的長度是No Virtual的長度加上一個void指針的長度。如果有一個或多個虛函數,編譯器都只在這個結構中插入一個單個指針,這個指針指向虛函數表。在32為的機器上,一個指針占3字節的空間,因此求sizeof得到4;如果是64位的機器,一個指針占8字節的空間,因此求sizeof則得到8.
六、關於抽象類和純虛函數
1、當繼承一個抽象類時,必須實現所有的純虛函數,否則繼承出的類也將是一個抽象類
2、聲明一個純虛函數,就等於告訴編譯器在vtbl中為函數保留一個位置,但在這個位置不放地址。只要有一個函數在類中被聲明為純虛函數,則vtbl就是不完全的。
3、純虛函數禁止對抽象類的函數以傳值方式調用,這是一種防止對象切片的方法。抽象類可以保證在向上類型轉換期間總是使用指針或引用。
4、對於純虛函數,如果要創建對象,必須要在派生類中定義。
七、什么是對象切片
在繼承的過程中,通常派生類不僅具有基類的特征,也具有自身的一些特征。當派生類向上進行類型轉換稱為基類時,就會發生那些自身的特征被切除,只保留繼承了基類的特征,這種現象就是對象切片。
例如:狗類繼承了寵物類,具有寵物類的名稱這個屬性,同時又有啃骨頭的特性,當狗類要被轉換為寵物類時,就必須拋棄自己愛啃骨頭的愛好,這樣只保留了對應於寵物類的那部分。流程如下:

八、虛函數和構造函數
1、由於基類構造函數總是在繼承類構造函數中被調用,這就確保了在派生類中,基類的所有成員都是有效的,即所有成員都已經建立。
2、虛機制在構造函數中不工作。有兩種理由:
A、在任何構造函數中,我們只能知道基類已被初始化,但不能知道哪個類是從這個基類繼承來的。但是,虛函數在繼承層次上是向前和向后調用。它可以調用派生類中的函數。
B、構造函數的vptr的狀態是由最后調用的構造函數確定的,這就意味着當最后調用的構造函數還沒有完成之前,當前的構造函數完全不知道這個對象是否是基於其他類的。但是,當這一系列的構造函數調用正發生時,每個構造函數都已經設置vptr指向它自己的vtbl,如果函數調用使用虛機制,它將只產生通過它自己的vtbl的調用,而不是最后派生的vtbl。
九、虛析構函數和析構函數
1、析構函數自最晚派生的類開始,並向上到基類。這就意味着每個析構函數知道它所在類從哪一個類派生而來,但不知道從它派生出哪些類。
2、析構函數可以為虛函數,因為這個對象已經知道它是什么類型,但是在構造期間就不知道了。一旦對象已被構造,它的vptr就已經被初始化,所以能發生虛函數調用。
3、虛構函數的純虛性的唯一效果是阻止基類的實例化
十、虛函數、純虛函數、抽象類的作用
虛函數的作用:每個類必須提供一個可以被調用的虛函數,但每個類可以按它們認為合適的任何方式處理。如果某個類不想做什么特別的事,可以借助於基類中提供的缺省處理函數。也就是說,虛函數的聲明是在告訴子類的設計者,"你必須支持虛函數,但如果你不想寫自己的版本,可以借助基類中的缺省版本。
純虛函數的作用:讓所有的類對象(主要是派生類對象)都可以執行純虛函數的動作,但類無法為純虛函數提供一個合理的缺省實現。所以類純虛函數的聲明就是在告訴子類的設計者,“你必須提供一個純虛函數的實現,但我不知道你會怎樣實現它”。
抽象類的主要作用是將有關的操作作為結果接口組織在一個繼承層次結構中,由它來為派生類提供一個公共的根,派生類將具體實現在其基類中作為接口的操作。所以派生類實際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實現這些語義,也可以再將這些語義傳給自己的子類。
