C++虛函數

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.若是是多重繼承(有虛函數覆蓋),每一個父類虛函數表中的位置被替換成了子類的函數指針。這樣,咱們就能夠任一靜態類型的父類來指向子類,並調用子類的函數;

相關文章
相關標籤/搜索