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 告訴編譯器,函數沒有主體,上面的虛函數是純虛函數