程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 初識C++之虛函數

初識C++之虛函數

編輯:C++入門知識

初識C++之虛函數


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;
}

這裡寫圖片描述
可以看到此時基類指針雖然指向的是派生類,但仍然調用的是基類的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 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關鍵字,也可以不加,但為了層次的清晰與代碼的易讀性,最好還是加上(那感覺前面說的都是廢話)。

③定義一個基類的指針,使之指向同一類族中需要調用的函數所在類的對象。

④通過該指針調用虛函數,此時調用的就是指針指向的對象的同名函數了。

用一個實例來說明上述實現機制:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
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 
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  
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函數仍為虛函數,會報錯。

   

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved