初识C++之虚函数
1、什么是虛函數(shù)
在基類中用virtual關鍵字修飾,并在一個或多個派生類中被重新定義的成員函數(shù),用法格式為:
virtual 函數(shù)返回類型 函數(shù)名(參數(shù)表)
{
函數(shù)體
}
虛函數(shù)是實現(xiàn)多態(tài)性的關鍵,通過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數(shù)。
看兩個例子:
①沒有定義基類的Fun函數(shù)為虛函數(shù):
可以看到此時基類指針雖然指向的是派生類,但仍然調(diào)用的是基類的Fun函數(shù)。
②定義了基類的Fun函數(shù)為虛函數(shù):
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;class Base { public:virtual void Fun() //注意這兒加了關鍵字virtual{cout << "Base::Fun()" << endl;} private:int data; };class Derived : public Base { public:void Fun(){cout << "Derived::Fun()" << endl;} private:int data; };int main() {Derived d;Base* pb = &d;pb->Fun();return 0; }
可以看到此時基類指針指向派生類后,其調(diào)用Fun函數(shù)時就是調(diào)用派生類的Fun函數(shù)了,從這兒也能看出虛函數(shù)是實現(xiàn)多態(tài)的關鍵。
2、虛函數(shù)的實現(xiàn)
①在基類中用virtual關鍵字修飾成員函數(shù),使之成為一個虛函數(shù),在派生類中重寫該函數(shù),使之完成新的功能。
②派生類中重寫該虛函數(shù)時,要求重寫的函數(shù)的函數(shù)名、參數(shù)列表、返回值都與基類虛函數(shù)相同,函數(shù)體根據(jù)需要選擇是否重寫。若不重寫,則直接完成繼承于基類。
PS:C++規(guī)定,當一個成員函數(shù)被定義為虛函數(shù)時,其派生類中的同名函數(shù)都自動變?yōu)樘摵瘮?shù),因此,派生類在重新定義該虛函數(shù)時,可加virtual關鍵字,也可以不加,但為了層次的清晰與代碼的易讀性,最好還是加上(那感覺前面說的都是廢話)。
③定義一個基類的指針,使之指向同一類族中需要調(diào)用的函數(shù)所在類的對象。
PS:基類與派生類的賦值關系可以參考下面這篇博客中的第六條:
http://blog.csdn.net/ljx_5489464/article/details/51114534
④通過該指針調(diào)用虛函數(shù),此時調(diào)用的就是指針指向的對象的同名函數(shù)了。
用一個實例來說明上述實現(xiàn)機制:
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;class Base { public:virtual void Fun() //加virtual關鍵字{cout << "Base::Fun()" << endl;} private:int data; };class Derived : public Base { public:void Fun(){//重寫Fun函數(shù)的函數(shù)體,當然,這兒不是必須要重寫的cout << "Derived::Fun()" << endl;} private:int data; };class Derived_Derived : public Derived { public:void Fun(){//重寫Fun函數(shù)的函數(shù)體,當然,這兒不是必須要重寫的cout << "Derived_Derived::Fun()" << endl;} private:int data; };int main() {Derived_Derived d;Derived* pb = &d;pb->Fun();return 0; }
我在Derived類中并沒有在Fun函數(shù)前加virtual關鍵字,但pb指針調(diào)用的卻是Derived_Derived類中的Fun函數(shù),而非Derived類中的,這就說明了只要基類的成員函數(shù)被定義為虛函數(shù)時,派生類的同名函數(shù)就會變?yōu)樘摵瘮?shù)。
注意:虛函數(shù)只能出現(xiàn)在類的繼承層次中,只能定義類的成員函數(shù)為虛函數(shù),不能定義類外的普通函數(shù)為虛函數(shù):
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;virtual void Fun() //注意這兒加了關鍵字virtual {cout << "Base::Fun()" << endl; }int main() {Fun();return 0; }以上代碼會報錯:
3、虛析構函數(shù)
析構函數(shù)執(zhí)行時先調(diào)用派生類的析構函數(shù),其次才調(diào)用基類的析構函數(shù)。如果析構函數(shù)不是虛函數(shù),而程序執(zhí)行時又要通過基類的指針去銷毀派生類的動態(tài)對象(用new運算符創(chuàng)建的),那么用delete銷毀對象時,只調(diào)用了基類的析構函數(shù),未調(diào)用派生類的析構函數(shù)。這樣會造成銷毀對象不完全。
看例子:
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Base { public: Base(){ cout << "Base()" << endl; }~Base() {cout << "~Base()" << endl; } private: int data; };class Derived: public Base { public: Derived(){cout << "Derived()" << endl;}~Derived(){cout << "~Derived()" << endl;} private: int data; }; int main() { Base *pb = new Derived;delete pb; return 0; }
可以看到,確實是只調(diào)用了基類的析構函數(shù),派生類的析構函數(shù)并沒有被調(diào)用。
當我把基類的析構函數(shù)改成這樣:
virtual ~Base() {cout << "~Base()" << endl; }再運行時,結(jié)果如下:
可以看到,此時不管是基類還是派生類的的析構函數(shù)都被調(diào)用了,為什么呢?
解析如下:
假設Derive類public繼承自Base類,并且基類的析構函數(shù)沒有定義為虛函數(shù),有如下調(diào)用:
Base* pb = new Derive;
delete pb;
這時候,delete pb時,編譯器會把pb當成Base的一個對象看待,因為pb是Base類型的,所以直接去調(diào)用基類的析構函數(shù);
若將基類的析構函數(shù)寫成virtual函數(shù),那么基類和派生類的析構函數(shù)會分別存放在自己的虛表中,這時再執(zhí)行delete pb時,會調(diào)用析構函數(shù),但現(xiàn)在虛構函數(shù)是虛函數(shù),所以會到虛表中去查找,而此時pb指向的剛好是一個派生類對象,所以通過虛表查找就找到了派生類的虛函數(shù),從而調(diào)用派生類的析構函數(shù)。
4、純虛函數(shù)
①定義:
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,即沒有函數(shù)體,但要求任何派生類都要實現(xiàn)自己的函數(shù)體。在基類中實現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0;“。
virtual void funtion()=0 ;
②引入原因 :
為了方便使用多態(tài)特性,我們常常需要在基類中定義虛擬函數(shù)。 但在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。 為了解決上述問題,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù),則編譯器要求在派生類中必須予以重寫以實現(xiàn)多態(tài)性。這樣就很好地解決了上述兩個問題。
③作用:
純虛函數(shù)的作用是在基類中為其派生類保留一個函數(shù)的名字,以便派生類根據(jù)需要對其進行實現(xiàn),從而實現(xiàn)多態(tài)性。
④抽象類
不用來定義對象,只是作為一種基本類型用作繼承的類,稱為抽象類,由于抽象類通常作為基類,所以也稱為抽象基類。包含純虛函數(shù)的類就是抽象類,它不能生成對象。
注意:
①純虛函數(shù)沒有函數(shù)體;
②純虛函數(shù)聲明形式最后的“=0;”并不是返回值是0,它只是起一個形式上的作用,告訴編譯器這是虛函數(shù);
③虛函數(shù)的聲明形式是一個語句,最后要加上“;”。
④純虛函數(shù)只有聲明,沒有函數(shù)體,所以它不具有函數(shù)功能,因此不能被調(diào)用,可以稱其為“徒有其表”。
⑤如果一個類中聲明了虛函數(shù),且在其派生類中沒有被實現(xiàn),那么在派生類中它仍為純虛函數(shù)。
最后用一個實例來剖析:
class Animal { public:virtual void Sleep() = 0; //這里聲明Sleep為純虛函數(shù) public:char sex[5];int age; };class Person: public Animal { public:virtual void Sleep(){cout << "Person lying down to sleep!" << endl;} public:char notionality[20]; //國籍char name[10]; };class Horse: public Animal { public:virtual void Sleep(){cout << "Horse sleep standing up!" << endl;} public:char rase[20]; //種族 };int main() {Animal a;return 0; }
此時用含有純虛函數(shù)Sleep的抽象類Animal來創(chuàng)建對象時,會報錯。
我把主函數(shù)改為調(diào)用抽象類Animal中的Sleep函數(shù):
int main() {Animal::Sleep();return 0; }
調(diào)用一個純虛函數(shù),會報錯。
我把Person修改了,去掉它對Sleep函數(shù)的實現(xiàn):
class Person: public Animal { public:char notionality[20]; //國籍char name[10]; };
此時Person類中沒有對Sleep函數(shù)進行實現(xiàn),因此,Person類中的Sleep函數(shù)仍為虛函數(shù),會報錯。
總結(jié)
- 上一篇: h5获取http请求头_nodejs 中
- 下一篇: 初识C++之函数重载