C++中虛函數的做用主要是實現了多態的機制,核心理念就是經過基類訪問派生類定義的函數。多態也就是用父類型別的指針指向其子類的實例,而後經過父類的指針調用實際子類的成員函數。這種技術可讓父類的指針有「多種形態」,這是一種泛型技術。所謂泛型技術,也就是試圖使用不變的代碼來實現可變的算法。好比:模板技術、RTTI技術,虛函數技術,要麼是試圖作到在編譯時決議,要麼試圖作到運行是決議。算法
靜態多態與動態多態:ide
靜態多態,也叫早綁定,也就是說程序在編譯階段根據參數個數肯定調用那個函數,例如函數
class Rect //矩形類 { public: int calcArea(int width); int calcArea(int width,int height); }; int main() { Rect.rect; rect.calcArea(10); rect.calcArea(10,20); return 0; }
動態多態,也叫晚綁定,是以封裝和繼承爲基礎,在運行時才決定調用那個函數,例如:spa
class Shape { public: virtual double calcArea();//虛函數 }; class Circle:public Shape { public: Circle(double r); double calcArea(); }; class Rect:public Shape { Rect(double width,double height); double calcArea(); }; int main() { Shape *shape1 = new Circle(4.0); Shape *shape2 = new Rect(3.0,5.0); shape1->calcArea(); shape2->calcArea(); return 0; }
注意:指針
1.基類聲明的虛函數,在派生類中也是虛函數,即便再也不使用virtual關鍵字,虛函數也實現了動態多態的方式,如上述main函數中,shape1和shape2分別調用各自成員calcArea函數;code
2.若是基類沒有用virtual修飾,即爲普通成員函數時,這時在main函數中,shape1和shape2調用的則爲基類的calcArea函數;對象
重載與重寫:blog
重載(overload),是指編寫一個與已有函數同名但參數表不一樣的函數;繼承
重寫(override),也稱爲「覆蓋」,是指派生類重寫基類的虛函數,重寫的函數必須有一致的參數表和返回值;接口
父類和子類出現同名函數稱爲隱藏。
父類對象.函數函數名(...); //調用父類的函數
子類對象.函數名(...); //調用子類的函數
子類對象.父類名::函數名(...); //子類調用從父類繼承來的函數。
父類和子類出現同名虛函數稱爲覆蓋
父類指針 = new 子類名(...); 父類指針->函數名(...);//調用子類的虛函數。
virtual關鍵字:
純虛函數,標誌自己爲一個抽象類,不能直接實例化,是用於規範派生類的行爲,實際上就是告訴派生類必須實現相同的函數接口
class Shape { public: virtual double calcArea()=0; // =0標誌一個虛函數爲純虛函數 };
虛析構函數,析構函數能夠是虛的,甚至是純虛的,一個若是用做於其餘類的基類時,它的析構函數必須是虛的,不然會致使內存泄漏,例如
class Shape { public: //virtual ~Shape(); ~Shape(); }; class Circle:public Shape { public: Circle(int x, int y, double r); ~Circle(); virtual double calcArea(); private: double mRadius; Coordinate *mpCenter; //座標類指針 }; Circle::Circle(int x, int y, double r) { mpCenter = new Coordinate(x,y); mRadius = r ; } Circle::~Circle() { delete m_pCenter; mpCenter = nullptr; } int main() { Shape *shape = new Circle(3, 5, 4.0); shape->calcArea(); delete shape; shape = nullptr; return 0; }
上面的程序中,若是在圓形的類中定義一個圓心的座標,而且座標是在堆中申請的內存,則在mian函數中經過父類指針操做子類對象的成員函數的時候是沒有問題的,但是在銷燬對象內存的時候則只是執行了父類的析構函數,子類的析構函數卻沒有執行,這會致使內存泄漏。
若是delete後邊跟父類的指針則只會執行父類的析構函數,若是delete後面跟的是子類的指針,那麼它即會執行子類的析構函數,也會執行父類的析構函數。
virtual在函數中的使用限制:
普通函數不能是虛函數,也就是說這個函數必須是某一個類的成員函數,不能夠是一個全局函數,不然會致使編譯錯誤。
靜態成員函數不能是虛函數 static成員函數是和類同生共處的,他不屬於任何對象,使用virtual也將致使錯誤。
內聯函數不能是虛函數 若是修飾內聯函數 若是內聯函數被virtual修飾,計算機會忽略inline使它變成存粹的虛函數。
構造函數不能是虛函數,不然會出現編譯錯誤。
虛函數表:
虛函數(Virtual Function)是經過一張虛函數表(Virtual Table)來實現的。簡稱爲V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,因此,當咱們用父類的指針來操做一個子類的時候,這張虛函數表就顯得由爲重要了,它就像一個地圖同樣,指明瞭實際所應該調用的函數。
1.通常狀況下,在虛函數表中,虛函數按照其聲明順序存放的,父類的虛函數在子類的虛函數前面;
2.若是子類中有虛函數重載了父類的虛函數,覆蓋的函數被放到了虛表中原來父類虛函數的位置,沒有被覆蓋的函數依舊;
3.若是是多重繼承(無虛函數覆蓋),每一個父類都有本身的虛表,子類的成員函數被放到了第一個父類(所謂的第一個父類是按照聲明順序來判斷的)的表中;
4.若是是多重繼承(有虛函數覆蓋),每一個父類虛函數表中的位置被替換成了子類的函數指針。這樣,咱們就能夠任一靜態類型的父類來指向子類,並調用子類的函數;