多型
在本節開始之前,建議您對指標和類繼承有充分的理解。如果以下任何陳述對您來說很奇怪,您應該回顧相應的章節。
陳述 | 在...中解釋 |
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 = ▭
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 (
ppoly1和
ppoly2的指標。然後,我們將對
rect和
trgl的引用賦給這些指標,因為它們都是派生自
CPolygon的類的物件,所以這兩個賦值操作都是有效的。
使用
*ppoly1和
*ppoly2而不是
rect和
trgl的唯一限制是,兩者都是
*ppoly1和
*ppoly2j
CPolygon*型別的,因此我們只能使用這些指標來引用
CRectangle和
CTriangle從
CPolygon繼承的成員。因此,當我們呼叫程式末尾的
area()成員時,我們必須直接使用物件
rect和
trgl而不是指標。
*ppoly1和
*ppoly2.
為了在指標指向類
area()時使用
CPolygon,該成員也應該在類
CPolygon中宣告,而不僅僅是在其派生類中,但問題在於
CRectangle和
CTriangle實現了
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 = ▭
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)都具有相同的成員。
寬度,
height,
set_values()和
area().
成員函式
area()在基類中被宣告為虛擬函式,因為它後來在每個派生類中被重新定義。如果您想驗證一下,可以嘗試移除
virtual關鍵字,並執行程式,結果將是
area()在
CPolygon中的宣告,而不是
0對所有三個多邊形,而不是
20,
10和
0。這是因為,與其呼叫每個物件對應的
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 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 = ▭
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 = ▭
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的指標型別,但動態分配的物件已宣告為直接具有派生類型別。