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