1、什麼是虛函數
在基類中用virtual關鍵字修飾,並在一個或多個派生類中被重新定義的成員函數,用法格式為:
virtual 函數返回類型 函數名(參數表)
{
函數體
}
虛函數是實現多態性的關鍵,通過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數。
看兩個例子:
①沒有定義基類的Fun函數為虛函數:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class Base
{
public:
void Fun()
{
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;
}
2、虛函數的實現 ②派生類中重寫該虛函數時,要求重寫的函數的函數名、參數列表、返回值都與基類虛函數相同,函數體根據需要選擇是否重寫。若不重寫,則直接完成繼承於基類。 ③定義一個基類的指針,使之指向同一類族中需要調用的函數所在類的對象。 ④通過該指針調用虛函數,此時調用的就是指針指向的對象的同名函數了。 用一個實例來說明上述實現機制: 注意:虛函數只能出現在類的繼承層次中,只能定義類的成員函數為虛函數,不能定義類外的普通函數為虛函數: 以上代碼會報錯: 3、虛析構函數 看例子: 當我把基類的析構函數改成這樣: 再運行時,結果如下: 可以看到,此時不管是基類還是派生類的的析構函數都被調用了,為什麼呢? 4、純虛函數 ②引入原因 : ③作用: ④抽象類 注意: 最後用一個實例來剖析: 我把主函數改為調用抽象類Animal中的Sleep函數: 我把Person修改了,去掉它對Sleep函數的實現:
可以看到此時基類指針雖然指向的是派生類,但仍然調用的是基類的Fun函數。<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPqLatqjS5cHLu/nA4LXERnVuuq/K/c6q0Om6r8r9o7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
#define _CRT_SECURE_NO_WARNINGS 1
#include
可以看到此時基類指針指向派生類後,其調用Fun函數時就是調用派生類的Fun函數了,從這兒也能看出虛函數是實現多態的關鍵。
①在基類中用virtual關鍵字修飾成員函數,使之成為一個虛函數,在派生類中重寫該函數,使之完成新的功能。
PS:C++規定,當一個成員函數被定義為虛函數時,其派生類中的同名函數都自動變為虛函數,因此,派生類在重新定義該虛函數時,可加virtual關鍵字,也可以不加,但為了層次的清晰與代碼的易讀性,最好還是加上(那感覺前面說的都是廢話)。
#define _CRT_SECURE_NO_WARNINGS 1
#include
我在Derived類中並沒有在Fun函數前加virtual關鍵字,但pb指針調用的卻是Derived_Derived類中的Fun函數,而非Derived類中的,這就說明了只要基類的成員函數被定義為虛函數時,派生類的同名函數就會變為虛函數。
#define _CRT_SECURE_NO_WARNINGS 1
#include
析構函數執行時先調用派生類的析構函數,其次才調用基類的析構函數。如果析構函數不是虛函數,而程序執行時又要通過基類的指針去銷毀派生類的動態對象(用new運算符創建的),那麼用delete銷毀對象時,只調用了基類的析構函數,未調用派生類的析構函數。這樣會造成銷毀對象不完全。
#define _CRT_SECURE_NO_WARNINGS 1
#include
可以看到,確實是只調用了基類的析構函數,派生類的析構函數並沒有被調用。
virtual ~Base()
{
cout << "~Base()" << endl;
}
解析如下:
假設Derive類public繼承自Base類,並且基類的析構函數沒有定義為虛函數,有如下調用:
Base* pb = new Derive;
delete pb;
這時候,delete pb時,編譯器會把pb當成Base的一個對象看待,因為pb是Base類型的,所以直接去調用基類的析構函數;
若將基類的析構函數寫成virtual函數,那麼基類和派生類的析構函數會分別存放在自己的虛表中,這時再執行delete pb時,會調用析構函數,但現在虛構函數是虛函數,所以會到虛表中去查找,而此時pb指向的剛好是一個派生類對象,所以通過虛表查找就找到了派生類的虛函數,從而調用派生類的析構函數。
①定義:
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,即沒有函數體,但要求任何派生類都要實現自己的函數體。在基類中實現純虛函數的方法是在函數原型後加“=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來創建對象時,會報錯。
int main()
{
Animal::Sleep();
return 0;
}
調用一個純虛函數,會報錯。
class Person: public Animal
{
public:
char notionality[20]; //國籍
char name[10];
};
此時Person類中沒有對Sleep函數進行實現,因此,Person類中的Sleep函數仍為虛函數,會報錯。