簡介
本章節(jié)會介紹在C++中虛函數(shù)及純虛函數(shù)的主要作用腾它,C++也是通過虛函數(shù)實現(xiàn)動態(tài)綁定报亩,本小節(jié)不會去講述動態(tài)綁定內(nèi)部實現(xiàn)原理吕座,具體可以參考[C++動態(tài)綁定原理].
結(jié)構(gòu)圖
-
虛函數(shù)
- 成員函數(shù)(member function)為虛函數(shù)(virtual function)
- 析構(gòu)函數(shù)為虛函數(shù)
-
純虛函數(shù)
- 成員函數(shù)為純虛函數(shù)(pure virtual function)
- 析構(gòu)函數(shù)為純虛函數(shù)
虛函數(shù)
-
成員函數(shù)為虛函數(shù)
在開始虛函數(shù)之前匣沼,先看一段代碼分析其中的問題。
#include <iostream>
class Animal {
public:
Animal() {}
~Animal(){}
void run(void) {
std::cout << "Animal run..." << std::endl;
}
};
class Cat : public Animal{
public:
Cat() : Animal(){}
void run(void) {
std::cout << "Cat run..." << std::endl;
}
};
int main(int argc,char** argv)
{
Animal *animal = new Cat; //創(chuàng)建Cat對象雷酪,然后執(zhí)行向上類型轉(zhuǎn)換
animal->run();
return 0;
}
/*輸出結(jié)果*/
//Animal run...
在以上代碼中淑仆,animal所指向的為Cat對象,但是當(dāng)調(diào) 用run函數(shù)的時候哥力,打印的值是"Animal run...",并不是"Cat run...",這是因為當(dāng)編譯器看到"animal"的指針類型是"Animal"時蔗怠,自然而然的就會調(diào)用Animal::run(),在編譯時期,就已經(jīng)確定所要調(diào)用的具體函數(shù)吩跋,這也就是所說的靜態(tài)綁定蟀淮。
在開始后面講解之前,需要先記住兩點知識:
1. 聲明一個虛函數(shù)需要使用關(guān)鍵字:virtual
2. 當(dāng)基類(或稱為父類)中的某函數(shù)A()被聲明為虛函數(shù)之后钞澳,在繼生類(或稱為子類)中重載的A(),即不使用virtual修飾,也是虛函數(shù)
現(xiàn)在對上面的代碼進行修改涨缚,將void Animal::run()
修改為虛函數(shù)轧粟,即改為virtual void Animal::run()
,觀察修改后的運行情況策治。
#include <iostream>
class Animal {
public:
Animal() {}
~Animal() {}
virtual void run(void) { //!!!!!!!!!!注意此處的修改
std::cout << "Animal run..." << std::endl;
}
};
class Cat : public Animal {
public:
Cat() : Animal() {}
void run(void) {
std::cout << "Cat run..." << std::endl;
}
};
int main(int argc, char** argv)
{
Animal *animal = new Cat; //創(chuàng)建Cat對象,然后執(zhí)行向上類型轉(zhuǎn)換
animal->run();
return 0;
}
//輸出結(jié)果:
//Cat run...
當(dāng)Animal::run()
為虛函數(shù)時兰吟,輸出結(jié)果為Cat run...,這也就是虛函數(shù)的作用通惫,此時在程序運行時去決定調(diào)用哪一個run()
函數(shù),這也就是動態(tài)綁定混蔼。在這里animal實際上指向的是Cat對象履腋,所以調(diào)用的是Cat::run()
。
-
析構(gòu)函數(shù)為虛函數(shù)
當(dāng)基類中包含虛成員函數(shù)的時候惭嚣,一般會把析構(gòu)函數(shù)
也定義為虛析構(gòu)函數(shù)虛析構(gòu)函數(shù)和虛成員函數(shù)在函數(shù)定義上是一樣的遵湖,只需要添加
virtual
關(guān)鍵字就可以了,這里主要介紹定義虛析構(gòu)函數(shù)的原因:- 為了通過基類指針正確釋放該指針?biāo)赶虻膶ο?該指針可能指向一個子類)
通過實例了解一下:
#include <iostream>
class Base1 {
public:
virtual void print(void) {
std::cout << "Base1 print" << std::endl;
}
~Base1(){
std::cout << "Base1's destructor" << std::endl;
}
};
class Derived1 : public Base1
{
public:
Derived1(int n) {
p = new char[n];
}
void print(void) {
std::cout << "Derived1 print" << std::endl;
}
~Derived1() {
delete[]p;
std::cout << "Derived1's destructor" << std::endl;
}
private:
char *p;
};
class Base2 {
public:
virtual void print(void) {
std::cout << "Base2 print" << std::endl;
}
virtual ~Base2() {
std::cout << "Base2's destructor" << std::endl;
}
};
class Derived2 : public Base2
{
public:
Derived2(int n) {
p = new char[n];
}
void print(void) {
std::cout << "Derived2 print" << std::endl;
}
~Derived2() {
delete[]p;
std::cout << "Derived2's destructor" << std::endl;
}
private:
char *p;
};
int main(int argc, char** argv)
{
std::cout << "Base1 test: \n";
Base1 *base1 = new Derived1(5);
base1->print();
delete base1;
std::cout << "\n\nBase2 test: \n";
Base2 *base2 = new Derived2(5);
base2->print();
delete base2;
return 0;
}
//輸出結(jié)果:
Base1 test:
Derived1 print
Base1's destructor
Base2 test:
Derived2 print
Derived2's destructor
Base2's destructor
base1實際指向Derived1對象晚吞,在Derived中通過new在堆上創(chuàng)建了5個char,但是當(dāng)執(zhí)行delete base1;
的時候延旧,只調(diào)用了Base1::~Base1()
,但卻沒有調(diào)用Derived1::~Derived1()
,最終導(dǎo)致這5個char無法釋放槽地,而導(dǎo)致內(nèi)存泄漏迁沫。
當(dāng)把Base2的析構(gòu)函數(shù)聲明為虛函數(shù)的時候,調(diào)用delete base2
,會先調(diào)用Derived2::~Derived2()
,然后再調(diào)用Base2::~Base2()
,所有內(nèi)存成功釋放捌蚊。
純虛函數(shù)
TODO