【c++基礎】虛函數的使用以及和成員函數的區別

前言

一直以來都知道虛函數的經典用法,但是除了本科時剛學c++的時候瞭解過,後來因爲做不同的項目在不同的語言之間跳轉(自覺都是淺嘗輒止),這些基本的東西都忘記了,現在重拾並記錄,權當鞏固基礎了。

經典用法

#include<iostream>
using namespace std;

//幾何體類
class Geometry{
public:
    virtual void draw(){
        cout<<"Geometry Drawing"<<endl;
    }
};

//矩形類
class Rect:public Geometry{
public:
    virtual void draw(){
        cout<<"Rect Drawing"<<endl;
    }
};

//圓類
class Circle:public Geometry{
public:
    virtual void draw(){
        cout<<"Circle Drawing"<<endl;
    }
};

void DrawSomething(Geometry *pG){
    pG->draw();
}
int main(){
    Geometry *geometrys[3];
    //1.init
    geometrys[0]=new Rect();
    geometrys[1]=new Geometry();
    geometrys[2]=new Circle();
    //2.draw all
    for(int i=0;i<3;i++){
        DrawSomething(geometrys[i]);
    }
    return 0;
}

運行結果:
Rect Drawing
Geometry Drawing
Circle Drawing

用虛函數就可以實現DrawSomething函數這樣接口簡單的函數,而不用寫DrawRectDrawCircle這樣的函數組。
另外這個例子不好的地方是,幾何體實際上可以作爲一個抽象的概念,應該限制它實例化、此時可以把其中的draw函數設置爲純虛函數virtual void draw()=0;.如此幾何體類爲一個抽象類,禁止實例化。

原理

首先來看一下c++對象模型圖(來自《Inside The C++ Object Model》)
c++對象模型

圖中可以看到只有非靜態成員變量和虛表指針是存在對象內的。靜態成員函數、成員函數、靜態變量是獨立存儲的(可以認爲所有的類對象都共有一份)。虛表指針指向的虛表中存儲了所有的虛函數。回到代碼中,當執行這句話的時候pG->draw();時,編譯器檢測到是需要執行虛函數,就會通過該對象實例的vptr指針找到虛函數的地址,進而調用。所以即使子類的指針被轉化爲父類指針調用,當我們需要使用虛函數時,依然是調用子類的虛函數(轉化的時候,vptr的沒有變)。

現在再來看反面,假設我們去掉了上面代碼所有的virtual 關鍵字。運行結果會是如何?結果是:

Geometry Drawing
Geometry Drawing
Geometry Drawing

從對象模型圖中可以看出一些差別。該變量是什麼類型,就會調用該類型的成員函數(我們的例中,都爲Geometry *類型),沒有一個vptr去做區分。有點類似於全局函數,如果對象或對象指針調用成員函數(非虛),實際上就是調用這個獨立於類實例的函數指針。

總的來說不像虛函數和對象實例緊密相連(通過虛表指針),普通成員函數和類實例是分離的(見模型圖)。但是由此產生的問題是,那成員函數如何去使用成員變量呢,實際上編譯器隱式的給成員函數加上一個參數,使得調用時可以把this指針傳進去,通過this指針就可以調用成員變量了。

補充

關於虛函數的實現原理,參考這篇博客 http://www.cnblogs.com/malecrab/p/5572730.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章