c++ 虛擬函式和純虛擬函式
靜態多型和動態多型–虛擬函式、純虛擬函式
靜態多型:程式在編譯階段就可以確定呼叫哪個函式。這種情況叫做靜態多型。比如過載
動態多型:在執行期間才可以確定最終呼叫的函式。需要通過虛擬函式+封裝+繼承實現。
虛擬函式
1、虛擬函式都必須有定義
2、虛擬函式一般用在繼承中。多個子類繼承同一基類,若在某種行為上不同的派生類有著自己的實現方式。這種情況我們就會用到多型。採用在基類中將此函式定義成虛擬函式,派生類中定義這個函式的不同實現。當我們使用基類的引用或指標呼叫一個虛成員函式時會執行動態繫結。因為直到執行的時候才能知道到底呼叫了哪個版本的虛擬函式,判斷依據是根據引用或指標所繫結的物件的真實型別。
3、若子類中重寫了父類的方法,但是父類中此方法並沒有設定為虛擬函式。那麼通過指向子類的指標或引用呼叫此方法的時候,呼叫的是父類的方法。
4、基類中某個函式一旦被宣告為虛擬函式,則在所有派生類中它都是虛擬函式,不需要在派生類中再一次通過virtual關鍵字指出該函式的性質。
5、當且僅當通過指標或引用呼叫虛擬函式時,才會在執行時解析該呼叫。
6、靜態成員函式不能被定義成虛擬函式。因為static成員函式是屬於類的。不屬於任何物件。
7、行內函數不能被定義成虛擬函式。因為函式的inline屬性是在編譯時確定的,而virtual屬性是在執行時確定的,因此這個兩個屬性是不可能同時定義的。
8、建構函式不能被定義成虛擬函式。
#include <iostream> using namespace std; class Father { public: Father() { cout << "Father Constructor" << endl; } void calcMethod() { cout << "Father calcMethod()" << endl; } virtual void virtualMethod() { cout << "Father virtualMethod()" << endl; } virtual void virtualCommon() { cout << "Father virtualCommon()" << endl; } ~Father() { cout << "Father disConstruct" << endl; } }; class Son:public Father { public: Son() { cout << "Son Constructor" << endl; } void calcMethod() { cout << "Son calcMethod()" << endl; } void virtualMethod() { cout << "Son virtualMethod()" << endl; } ~Son() { cout << "Son disConstruct" << endl; } }; int main() { Father *f = new Son(); //先執行father建構函式,在執行son建構函式 f->calcMethod(); //Father calcMethod()。父,子--->父。如果父類中的方法有自己的實現,則會去呼叫父類的方法。 見上述第3條。 f->virtualMethod(); //Son virtualMethod()。父虛,子虛--->子。若把父類中的方法定義成虛擬函式,子類中有自己的實現,則會去掉用指向的子類中對應的方法。 見上述第2條。 f->virtualCommon(); //Father virtualCommon()。父虛,子無--->父。若把父類中的方法定義成虛擬函式,子類中有沒有覆蓋這個虛擬函式,則會直接呼叫父類的虛擬函式。 delete f; return 0; }
控制檯列印:
Father Constructor
Son Constructor
Father calcMethod()
Son virtualMethod()
Father disConstruct
可以發現呼叫delete的時候只執行了父類的解構函式,沒有執行子類的解構函式。因為父類的解構函式不是虛擬函式,參參考上文第3點,這不是造成了記憶體洩漏?怎麼解決這個問題呢?
純虛擬函式
純虛擬函式相當於定義了一個介面,不同的子類必須定義自己的實現。
#include <iostream> using namespace std; //抽象類 class Father { public: virtual void calcMem() = 0; //=0表示這是個純虛擬函式。純虛擬函式不需要定義,沒有方法體。 virtual void anotherMethod() = 0; //純虛擬函式,也可以定義。 }; void Father::anotherMethod() { cout << "Father anotherMethod" << endl; } class Son:public Father { public: virtual void calcMem() //這裡的virtual也可以不顯示宣告。 { cout << "son calcMem" << endl; } void anotherMethod() { cout << "Son anotherMethod" << endl; } }; int main() { Son *s = new Son(); s->calcMem(); //son calcMem s->anotherMethod(); //Son anotherMethod Father *f = new Son(); f->calcMem(); //son calcMem f->anotherMethod(); //Son anotherMethod f->Father::anotherMethod(); //Father anotherMethod。也可以顯示的呼叫父類的方法 return 0; }
控制檯列印:
son calcMem
Son anotherMethod
son calcMem
Son anotherMethod
Father anotherMethod
虛解構函式
delete後面若跟父類指標,則只會執行父類的解構函式。若delete後面跟子類的指標,那麼即會執行子類的解構函式,也會執行父類的解構函式。
可以通過在基類中把解構函式定義成虛擬函式來解決這個問題。因為若不定義成虛擬函式,通過指向子類的指標或引用呼叫delete的時候會預設執行父類的解構函式(可參考上述虛擬函式介紹的第3條),而不會去執行子類的解構函式。
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout << "Father Constructor" << endl;
}
virtual ~Father()
{
cout << "Father Destruct" << endl;
}
};
class Son:public Father
{
public:
Son()
{
cout << "Son Constructor" << endl;
}
~Son()
{
cout << "Son Destruct" << endl;
}
};
int main()
{
Father *f = new Son();
delete f;
cout << "--------------" << endl;
Son *s = new Son();
delete s;
return 0;
}
控制檯列印
Father Constructor
Son Constructor
Son Destruct
Father Destruct
--------------
Father Constructor
Son Constructor
Son Destruct
Father Destruct
虛擬函式和純虛擬函式的比較
(1)如果基類中定義了虛擬函式AF,派生類中對於這個虛擬函式可以覆蓋也可以不覆蓋。
派生類中如果覆蓋了這個虛擬函式AZ,則通過指向子類的指標或引用呼叫的就是派生類中AZ
如果派生類中沒有對這個AF進行覆蓋,那麼通過指向子類的指標或引用呼叫的就是基類中AF
(2)如果基類中定義了純虛擬函式AAF,相當於是個介面。那麼在派生類中就必須覆蓋基類的這個純虛擬函式。