多型

在本節開始之前,建議您對指標和類繼承有充分的理解。如果以下任何陳述對您來說很奇怪,您應該回顧相應的章節。

陳述在...中解釋
int a::b(int c) { }
a->b資料結構
class a: public b { };友元和繼承

指向基類的指標

派生類的關鍵特性之一是,指向派生類的指標與指向其基類的指標是型別相容的。多型性就是利用這種簡單但強大且通用的特性,將面向物件的方法發揮到極致。

我們將從重寫上一節關於矩形和三角形的程式開始,並考慮這種指標相容性屬性。

// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
  };

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area ()
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << rect.area() << endl;
  cout << trgl.area() << endl;
  return 0;
}
20
10

在函式main中,我們建立了兩個指向類物件CPolygon (ppoly1ppoly2的指標。然後,我們將對recttrgl的引用賦給這些指標,因為它們都是派生自CPolygon的類的物件,所以這兩個賦值操作都是有效的。

使用*ppoly1*ppoly2而不是recttrgl的唯一限制是,兩者都是*ppoly1*ppoly2jCPolygon*型別的,因此我們只能使用這些指標來引用CRectangleCTriangleCPolygon繼承的成員。因此,當我們呼叫程式末尾的area()成員時,我們必須直接使用物件recttrgl而不是指標。*ppoly1*ppoly2.

為了在指標指向類area()時使用CPolygon,該成員也應該在類CPolygon中宣告,而不僅僅是在其派生類中,但問題在於CRectangleCTriangle實現了area的不同版本,因此我們無法在基類中實現它。這時,虛成員就派上用場了。

虛成員

可以在其派生類中重新定義的類成員被稱為虛成員。為了將類成員宣告為虛成員,我們必須在其宣告前加上關鍵字virtual:

// virtual members
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area ()
      { return (0); }
  };

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area ()
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon poly;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  CPolygon * ppoly3 = &poly;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly3->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  cout << ppoly3->area() << endl;
  return 0;
}
20
10
0

現在,這三個類(CPolygon, CRectangleCTriangle)都具有相同的成員。寬度, height, set_values()area().

成員函式area()在基類中被宣告為虛擬函式,因為它後來在每個派生類中被重新定義。如果您想驗證一下,可以嘗試移除virtual關鍵字,並執行程式,結果將是area()CPolygon中的宣告,而不是0對所有三個多邊形,而不是20, 100。這是因為,與其呼叫每個物件對應的area()函式(分別是CRectangle::area(), CTriangle::area()CPolygon::area()),CPolygon::area()將在所有情況下被呼叫,因為呼叫是透過一個型別為CPolygon*.

的指標進行的。virtual因此,

關鍵字的作用是允許派生類中與基類同名的成員被正確呼叫,尤其是在指標型別為指向基類的指標但實際上指向派生類物件的情況下,如以上示例所示。

宣告或繼承了虛擬函式的類稱為*多型類*。CPolygon請注意,儘管它具有虛特性,我們仍然可以宣告一個型別為area()的物件,並呼叫其自身的

函式,該函式始終返回0。

抽象基類CPolygon抽象基類與我們上一示例中的area()類非常相似。唯一的區別在於,在上一示例中,我們為類為CPolygon的物件(如物件poly)定義了一個有效的area()函式,具有最小的功能;而在抽象基類中,我們可以完全不實現該=0成員函式。這可以透過在函式聲明後附加

(等於零)來實現。

1
2
3
4
5
6
7
8
9
// abstract class CPolygon
class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area () =0;
};

一個抽象基類CPolygon可以這樣寫:=0注意我們附加了virtual int area ()

而不是指定函式的實現。這種函式稱為*純虛擬函式*,所有包含至少一個純虛擬函式的類都是*抽象基類*。

抽象基類與普通多型類之間的主要區別在於,由於抽象基類中至少有一個成員缺少實現,我們無法建立它的例項(物件)。

1
CPolygon poly;

但是,一個無法例項化物件的類並非完全無用。我們可以建立指向它的指標,並利用它的所有多型能力。因此,像

1
2
CPolygon * ppoly1;
CPolygon * ppoly2;

這樣的宣告對於我們剛剛宣告的抽象基類來說是不合法的,因為它試圖例項化一個物件。然而,以下指標

將是完全有效的。CPolygon這是因為

包含一個純虛擬函式,因此它是一個抽象基類。但是,指向此抽象基類的指標可以指向派生類物件。

// abstract base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  return 0;
}
20
10

完整的示例如下:CPolygon*如果您仔細檢視程式,您會發現我們使用了一種獨特的指標型別(CPolygon)來引用不同但相關的類物件。這可能非常有用。例如,現在我們可以建立一個抽象基類area()的成員函式,該函式即使CPolygon本身沒有該函式的實現,也能將

// pure virtual members can be called
// from the abstract base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  return 0;
}
20
10

函式的結果列印到螢幕上。

虛成員和抽象類賦予C++多型特性,這些特性使面向物件程式設計成為大型專案中非常有用的工具。當然,我們已經看到了這些特性的非常簡單的用法,但這些特性可以應用於物件陣列或動態分配的物件。

// dynamic allocation and polymorphism
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CPolygon * ppoly1 = new CRectangle;
  CPolygon * ppoly2 = new CTriangle;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  delete ppoly1;
  delete ppoly2;
  return 0;
}
20
10

讓我們用同一個例子結束,但這次使用動態分配的物件。請注意,ppoly

1
2
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;

指標被宣告為指向CPolygon的指標型別,但動態分配的物件已宣告為直接具有派生類型別。
Index
目錄