1. 程式人生 > >C++ 多型與虛擬函式

C++ 多型與虛擬函式

多型按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是通過繼承關聯時,就會用到多型。C++ 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式。下面的例項中,基類 Shape 被派生為兩個類,如下所示:


#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};
// 程式的主函式
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 儲存矩形的地址
   shape = &rec;
   // 呼叫矩形的求面積函式 area
   shape->area();
 
   // 儲存三角形的地址
   shape = &tri;
   // 呼叫三角形的求面積函式 area
   shape->area();
   
   return 0;
}

當上面的程式碼被編譯和執行時,它會產生下列結果:

Parent class area
Parent class area

導致錯誤輸出的原因是,呼叫函式 area() 被編譯器設定為基類中的版本,這就是所謂的靜態多型,或靜態連結 - 函式呼叫在程式執行前就準備好了。有時候這也被稱為早繫結,因為 area() 函式在程式編譯期間就已經設定好了。

但現在,讓我們對程式稍作修改,在 Shape 類中,area

() 的宣告前放置關鍵字 virtual,如下所示:


class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

修改後,當編譯和執行前面的例項程式碼時,它會產生以下結果:

Rectangle class area
Triangle class area

 

此時,編譯器看的是指標的內容,而不是它的型別。因此,由於 tri 和 rec 類的物件的地址儲存在 *shape 中,所以會呼叫各自的 area() 函式。

正如您所看到的,每個子類都有一個函式 area() 的獨立實現。這就是多型的一般使用方式。有了多型,您可以有多個不同的類,都帶有同一個名稱但具有不同實現的函式,函式的引數甚至可以是相同的。

虛擬函式

虛擬函式 是在基類中使用關鍵字 virtual 宣告的函式。在派生類中重新定義基類中定義的虛擬函式時,會告訴編譯器不要靜態連結到該函式。

我們想要的是在程式中任意點可以根據所呼叫的物件型別來選擇呼叫的函式,這種操作被稱為動態連結,或後期繫結

純虛擬函式

您可能想要在基類中定義虛擬函式,以便在派生類中重新定義該函式更好地適用於物件,但是您在基類中又不能對虛擬函式給出有意義的實現,這個時候就會用到純虛擬函式。

我們可以把基類中的虛擬函式 area() 改寫如下:


class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};

 

= 0 告訴編譯器,函式沒有主體,上面的虛擬函式是純虛擬函式

 

哪些函式不能設定為虛擬函式

設定虛擬函式須注意:
1:只有類的成員函式才能說明為虛擬函式;
2:靜態成員函式不能是虛擬函式;
3:行內函數不能為虛擬函式;
4:建構函式不能是虛擬函式;
5:解構函式可以是虛擬函式,而且通常宣告為虛擬函式。

虛擬函式可以實現多型,那麼那些函式不能申明為虛擬函式呢?

1.普通的函式

因為普通函式只能被overload(過載),不能被override,也不能被繼承,所以在編譯的時候就繫結函式,所以不能申明為virtual,沒有意義!

2.建構函式

這個也很簡單。主要因為建構函式是用來確定初始化物件的,而virtual主要是為了在不瞭解具體的情況下實現動態繫結,呼叫不同型別中合適的成員函式而存在的,現在物件都沒產生,怎麼能實現多型呢。一個是為了具體化,一個是為了在不同物件型別中確定合適的函式,這是不可能的!

此外,建構函式不能被繼承,所以不能virtual;建構函式是系統預設提供或者自己寫的,並且和類名相同,就算繼承了也不是自己的了,所以不能被繼承;

建構函式是在為了建立初始化物件存在的,物件不存在實現多型是不可能的;

3.行內函數

inline函式在編譯時被展開,在呼叫處將整個函式替換為程式碼塊,省去了函式跳轉的時間,提高了SD,減少函式呼叫的開銷,虛擬函式是為了繼承後物件能夠準確的呼叫自己的函式,執行相應的動作。

主要的原因是:inline函式在編譯時被展開,用函式體去替換函式,而virtual是在執行期間才能動態繫結的,這也決定了inline函式不可能為虛擬函式。(inline函式體現的是一種編譯機制,而virtual體現的是一種動態執行機制)

4.靜態成員函式

靜態成員函式是類的組成部分,但是不是任何物件的組成部分,所有物件共享一份,沒有必要動態繫結,也不能被繼承【效果能,但機理不能。靜態成員函式就一份實體,在父類裡;子類繼承父類時也無需拷貝父類的靜態函式,但子類可以使用父類的靜態成員函式】,並且靜態成員函式只能訪問靜態變數。所以不能為virtual。

5.友員函式

友員函式不是類的成員函式,C++不支援友員被繼承,所以不能為virtual。