## 4.4 多態 (Polymorphism)
為了能更好的理解本節內容,你需要清楚的知道怎樣使用指針pointers 和類之間的繼承 inheritance between classes。建議如果你覺得以下這些表達式比較生疏的的話, 請復習指定的章節:
~~~
int a::b(c) {}; // 類Classes (Section 4.1)
~~~
~~~
a->b // 指針和對象pointers and objects (Section 4.2)
~~~
~~~
class a: public b; // 類之間的關系Relationships between classes (Section 4.3)
~~~
### 基類的指針(Pointers to base class)
繼承的好處之一是**一個指向子類(derived class)的指針與一個指向基類(base class)的指針是type-compatible的。** 本節就是重點介紹如何利用C++的這一重要特性。例如,我們將結合C++的這個功能,重寫前面小節中關于長方形rectangle 和三角形 triangle 的程序:
| // pointers to base class
~~~
#include <iostream.h>
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 (void) {
return (width * height);
}
};
class CTriangle: public CPolygon {
public:
int area (void) {
return (width * height / 2);
}
};
int main () {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = ▭
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 中定義了兩個指向class CPolygon的對象的指針,即 *ppoly1 和 *ppoly2。 它們被賦值為rect 和 trgl的地址,因為rect 和 trgl是CPolygon 的子類的對象,因此這種賦值是有效的。
使用*ppoly1 和 *ppoly2 取代rect 和trgl 的唯一限制是*ppoly1 和 *ppoly2 是CPolygon* 類型的,因此我們只能夠引用CRectangle 和 CTriangle 從基類CPolygon中繼承的成員。正是由于這個原因,我們不能夠使用*ppoly1 和 *ppoly2 來調用成員函數 area(),而只能使用rect 和 trgl來調用這個函數。
要想使CPolygon 的指針承認area()為合法成員函數,必須在基類中聲明它,而不能只在子類進行聲明(見下一小節)。
### 虛擬成員(Virtual members)
如果想在基類中定義一個成員留待子類中進行細化,我們必須在它前面加關鍵字virtual ,以便可以使用指針對指向相應的對象進行操作。
請看一下例子:
| // virtual members
~~~
#include <iostream.h>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a;
height=b;
}
virtual int area (void) { return (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 poly;
CPolygon * ppoly1 = ▭
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, CRectangle 和 CTriangle) 都有同樣的成員:width, height, set_values() 和 area()。
area() 被定義為virtual 是因為它后來在子類中被細化了。你可以做一個試驗,如果在代碼種去掉這個關鍵字(virtual),然后再執行這個程序,三個多邊形的面積計算結果都將是 0 而不是20,10,0。這是因為沒有了關鍵字virtual ,程序執行不再根據實際對象的不用而調用相應area() 函數(即分別為CRectangle::area(), CTriangle::area() 和 CPolygon::area()),取而代之,程序將全部調用CPolygon::area(),因為這些調用是通過CPolygon類型的指針進行的。
因此,關鍵字virtual 的作用就是在當使用基類的指針的時候,使子類中與基類同名的成員在適當的時候被調用,如前面例子中所示。
**注意**,雖然本身被定義為虛擬類型,我們還是可以聲明一個CPolygon 類型的對象并調用它的area() 函數,它將返回0 ,如前面例子結果所示。
### 抽象基類(Abstract base classes)
基本的抽象類與我們前面例子中的類CPolygon 非常相似,唯一的區別是在我們前面的例子中,我們已經為類CPolygon的對象(例如對象poly)定義了一個有效地area()函數,而在一個抽象類(abstract base class)中,我們可以對它不定義,而簡單得在函數聲明后面寫 =0 (等于0)。
類CPolygon 可以寫成這樣:
// abstract class CPolygon
~~~
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a;
height=b;
}
virtual int area (void) =0;
};
~~~
注意我們是如何在virtual int area (void)加 =0 來代替函數的具體實現的。這種函數被稱為純虛擬函數(pure virtual function),而所有包含純虛擬函數的類被稱為抽象基類(abstract base classes)。
抽象基類的最大不同是它不能夠有實例(對象),但我們可以定義指向它的指針。因此,像這樣的聲明:
`CPolygon poly;`
對于前面定義的抽象基類是不合法的。
然而,指針:
`CPolygon * ppoly1;
CPolygon * ppoly2`
是完全合法的。這是因為該類包含的純虛擬函數(pure virtual function) 是沒有被實現的,而又不可能生成一個不包含它的所有成員定義的對象。然而,因為這個函數在其子類中被完整的定義了,所以生成一個指向其子類的對象的指針是完全合法的。
下面是完整的例子:
| // virtual members
~~~
#include <iostream.h>
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 = ▭
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()的結果打印到屏幕上,而不必考慮具體是為哪一個子類。
| // virtual members
~~~
#include <iostream.h>
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 = ▭
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
return 0;
}
~~~
| 20
10 |
記住,this 代表代碼正在被執行的這一個對象的指針。
抽象類和虛擬成員賦予了C++ 多態(polymorphic)的特征,使得面向對象的編程object-oriented programming成為一個有用的工具。這里只是展示了這些功能最簡單的用途。想象一下如果在對象數組或動態分配的對象上使用這些功能,將會節省多少麻煩。