純虛函數就像是java 中的接口函數,不能直接實例化,必須被派生類繼承,而後對基類中的虛函數進行實現。 虛函數的使用就是爲了方便多態的使用,經常須要在基類中定義虛函數,而後對基類進行繼承,再對基類中的虛函數進行重載。java
- 普通函數不能是虛函數,也就是說虛函數的定義必須在某個類中。虛函數不能夠是一個全局函數,不能夠單獨在類外定義,不然會致使編譯錯誤。
- 靜態成員函數不能是虛函數,即static成員函數是和類同生共處的,它不屬於任何一個對象,使用virtual也將致使錯誤。
- 構造函數不能是虛函數,不然會形成編譯錯誤
- 內聯函數不能是虛函數,若是內聯函數被virtual修飾,計算機會忽略inline使之變成純虛函數。
- 重載的幾個函數必須在同一個類中,覆蓋的函數必須在有繼承關係的不一樣類中
- 重載的函數必須函數名相同,參數列表不一樣。覆蓋的幾個函數必須函數名、參數、返回值都相同。
函數重載中,參數列表不一樣的目的就是爲了,在函數調用時編譯器可以經過參數來判斷程序是在調用哪一個函數。 這也很天然的解釋了爲何函數不能經過返回值不一樣來重載,由於程序在調用函數時頗有可能不關心返回值,編譯器就沒法從代碼中看書程序在調用的是哪一個函數。- 覆蓋的函數前必須加關鍵字virtual,重載和virtual沒有任何關係。
- 若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不管有無virtual關鍵字,積累的函數將被隱藏(注意別與重載混淆)。
- 若是派生類的函數與積累的函數同名,而且參數也相同,可是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
C++語言爲咱們提供了一種語法結構,經過它能夠指明,一個虛擬函數只是提供了一個可被子類型改寫的接口。可是,它自己並不能經過虛擬機制被調用,這就是純虛擬函數(pure virtual function)。純虛函數的聲明以下所示:函數
class Shape { public: virtual double calcArea()//虛函數 { //////// } virtual double calcPerimeter() = 0;//純虛函數 ////純虛函數沒有函數體,同時在定義的時候函數名後腰加 」=0「 }
什麼是函數指針?
指針指向函數就叫函數指針。函數的本質是一段二進制代碼,咱們能夠經過指針指向這段代碼的開頭。計算就會從這個開頭開始執行,直到函數結束爲止。
函數的指針和普通的指針本質上是同樣的,都是由4個基本的內存單元組成,存儲着內存的地址,這個地址就是函數的首地址。
多態的實現原理。指針
- 虛函數指針類中除了定義的函數成員,還有一個成員是虛函數表指針(佔4個基本內存單元),這個指針指向一個虛函數表的起始位置,這個表會與類的定義同時出現,這個表存放着該類的虛函數指針,調用的時候能夠找到該類的虛函數表指針,經過虛函數表指針找到虛函數表。經過虛函數表的偏移找到函數的入口地址,從而找到要使用的虛函數。
- 當實例化一個該類的子類對象的時候,若是該類的子類沒有定義虛函數,可是卻從父類中繼承了虛函數,因此在實例化該類子類對象的時候也會產生一個虛函數表,這個虛函數表是子類的虛函數表,可是記錄的子類的虛函數地址倒是與父類是同樣的。因此,經過子類對象的虛函數表指針找到本身的虛函數表,在本身的虛函數表找到的要執行的函數指針也是父類的相應函數入口的地址。
- 若是咱們在子類中定義了從父類繼承來的虛函數,對於父類來講狀況是不變的,對於子類來講它的虛函數表與以前的虛函數表是同樣的,可是此時子類定義了本身的(從父類那繼承來的)相應函數,因此它的虛函數表當中關於這個函數的指針就會覆蓋掉原有的指向父類函數的指針的值。
換句話說就是指向了本身定義的相應函數,這樣若是用父類的指針,指向子類的對象,就會經過子類對象當中的虛函數表指針找到子類的虛函數表,從而經過子類的虛函數表找到子類的相應虛函數的地址,而此時的地址已是該函數本身定義的虛函數入口地址,而不是父類的相應虛函數入口地址,因此執行的將會是子類當中的虛函數,這就是多態的原理。
父類和子類出現同名函數稱爲隱藏code
- 父類對象.函數名()
調用父類的函數- 子類對象.函數名()
調用子類函數- 子類對象.父類名::函數名()
子類調用從父類繼承來的函數
父類和子類出現同名虛函數稱爲覆蓋對象
- 父類指針=new 子類名(……);
- 父類指針->函數名(……);
調用子類的虛函數
虛析構函數的實現原理繼承
- 虛析構函數的實現原理:
當咱們在父類中經過virtual修飾析構函數以後,經過父類指針指向子類對象,經過delete接父類指針就能夠釋放掉子類對象。
執行完子類的析構函數就會執行父類的析構函數。
原理:
若是父類當中定義了虛析構函數,那麼父類的析構函數表當中就會有一個父類的虛析構函數指針,指向的是父類的虛析構函數,子類虛析構函數表當中也會產生一個子類的虛析構函數的入口指針,指向的是子類的虛析構函數,這個時候使用父類的指針指向子類的對象,delete接父類指針,就會經過指向的子類的對象找到子類的虛函數表指針,從而找到虛函數表,在虛函數表中找到子類的虛析構函數,從而使得子類的析構函數得以執行,子類的析構函數執行以後系統會自動執行父類的虛析構函數。這個是虛析構函數的實現原理。
純虛函數的實現原理
在虛函數原理的基礎上,虛函數表中,虛函數的地址是一個有意義的值,若是是純虛函數就實實在在的寫一個0 含有純虛函數的類被稱爲抽象類
含有純虛函數的類被稱爲抽象類。哪怕類中只有一個純虛函數,那麼這個類也是一個抽象類,純虛函數沒有函數體,因此抽象類不容許實例化對象,抽象類的子類也能夠是一個抽象類。
抽象子類只有把抽象類中全部純虛函數都作了實現才能夠實例化對象。接口
僅含有純虛函數的類稱爲接口類
若是在抽象類中僅含有純虛函數而不含其餘東西,咱們稱之爲接口類。內存
- 沒有數據成員
- 僅有成員函數
- 成員函數都是純虛函數
class Shape { virtual double calcArea() = 0; virtual double calcPerimeter() = 0; }