1、什麼是虛函數?來看看虛函數的官方定義:
在某基類中聲明爲 virtual 並在一個或多個派生類中被重新定義的成員函數,簡單來說,就是被virtual關鍵字修飾的成員函數。
作用:實現多態性(動多態),動多態又稱動態綁定或晚綁定。
2.那爲什麼要使用虛函數呢?
結論:實現因對象的不同而調用其相應的函數
可以讓成員函數操作一般化,用基類的指針指向不同的派生類的對象時, 基類指針調用其虛成員函數,則會調用其真正指向對象的成員函數, 而不是基類中定義的成員函數(只要派生類改寫了該成員函數)。若不是虛函數,則不管基類指針指向的哪個派生類對象,調用時都 會調用基類中定義的那個函數。
文字理解起來不太形象,來代碼演示一下;
#include<iostream>
using namespace std;
class Father
{
public:
void print()
{
cout<<"This is Father"<<endl;
}
};
class Son : public Father
{
public:
void print()
{
cout<<"This is Son"<<endl;
}
};
int main()
{
Father a;
Son b;
Father *p1 = &a;
Father *p2 = &b;
p1->print();
p2->print();
return 0;
}
運行結果爲:父類Show函數、父類show函數
用父類指針指向子類對象,如果不將Show函數聲明爲虛函數,最終調用的是父類Show函數,而不是我們派生後重寫的Show函數。如果將Show函數聲明爲虛函數,
class Father
{
public:
virtual void print(){cout<<"This is Father"<<endl;}
};
class Son : public Father
{
public:
void print(){cout<<"This is Father"<<endl;}
};
int main()
{
Father a;
Son b;
Father *p1 = &a;
Father *p2 = &b;
p1->print();
p2->print();
return 0;
}
毫無疑問,class Father的成員函數print()已經成了虛函數,那麼class Son的print()成了虛函數了嗎?回答是Yes,我們只需在把基類的成員函數設爲virtual,其派生類的相應的函數也會自動變爲虛函數。所以,class Son的print()也成了虛函數。那麼對於在派生類的相應函數前是否需要用virtual關鍵字修飾,那就是你自己的問題了(語法上可加可不加,不加的話編譯器會自動加上,但爲了閱讀方便和規範性,建議加上)。
現在重新運行main的代碼,這樣輸出的結果就是This is Father和This is Son了。
3.虛函數的應用
進一步思考,爲什麼要用父類指針指向子類對象呢?這根本不需要聲明爲虛函數啊,直接定義一個子類對象,或是用子類指針指向子類對象,再來調用print函數,即使不用聲明爲虛函數,也能調用我們派生後重寫的print函數。
雖然這樣說,但是實際開發過程中不是這樣的,當我們使用一些類庫、框架的時候,這些類庫、框架是事先就寫好的。我們在使用的時候不能直接修改類庫的源碼,我們只能派生類庫中的類來覆蓋一些成員函數以實現我們的功能,但這些成員函數有的是由框架調用的。這種情況下,用虛函數是很好的辦法。
4.那些函數可以成爲虛函數?
成爲虛函數的條件:1.能取地址 2.依賴對象調用
- 析構函數:可以(派生類裏的析構函數最好是虛函數,否則派生類中如有空間的開闢會造成派生類內存泄漏)
- 普通的類成員方法:可以
以下函數不能成爲虛函數:
- 構造函數:不能,(此時對象還沒有產生)
- static成員方法:不能。(不依賴對象調用)
- inline函數:不能。(inline函數在編譯時被展開,用函數體去替換函數,而虛函數是在運行期間才動態綁定。)
- 普通的方法:不能,(友元函數不屬於類的成員函數,只能重載,不能覆蓋,函數覆蓋發生在繼承關係中)
- 友元:不能(友元函數不屬於類的成員函數,不能被繼承)
這篇是關於虛函數的由來,下一篇更虛函數的實現。