許多著名語言中最優秀的特征,如從Algo168中吸取了操作符重載機制等。由於C++語言具有與C語言壹
樣的高執行效率,並容易被熟悉C語言的軟件人員接受,因而很快得以流行。但這種混合型面向對象的
程序設計語言是壹種新的程序設計語言,人們對它許多潛在的性能(封裝、繼承、多態等)還沒有充分
地理解和應用,沒有充分發揮其優勢。多態性是面向對象系統的重要概念之壹,它指的是同樣的消息
能被發送到父類的對象和它的子類的對象。本文重點討論多態性在程序設計中的應用。
1 多態性的實現形式
從廣義上說,多態性是指壹段程序能夠處理多種類型對象的能力。在C++語言中,這種多態性可以
通過強制多態、重載多態、類型參數化多態、包含多態4種形式來實現。類型參數化多態和包含多態統
稱為壹般多態性,用來系統地刻畫語義上相關的壹組類型。重載多態和強制多態統稱為特殊多態性,
用來刻畫語義上無關聯的類型間的關系。
包含多態是指通過子類型化,1個程序段既能處理類型T的對象,也能夠處理類型T的子類型S的對
象,該程序段稱為多態程序段。公有繼承能夠實現子類型。在包含多態中,1個對象可以被看作屬於不
同的類,其間包含關系的存在意味著公***結構的存在。包含多態在不少語言中存在,如整數類型中的
子集構成1個子類型。每壹個子類型中的對象可以被用在高壹級的類型中,高壹級類型中的所有操作可
用於下壹級的對象。在C++中公有繼承關系是壹種包含多態,每壹個類可以直接公有繼承父類或多個父
類,如語句class D?public P1,public P2{……};表示類D分別是類P1和類P2的子類型。
類型參數化多態是指當1個函數(類)統壹地對若幹類型參數操作時,這些類型表現出某些公***的語
義特性,而該函數(類)就是用來描述該特性的。在類型參數化多態中,1個多態函數(類)必須至少帶有
1個類型參數,該類型參數確定函數(類)在每次執行時操作數的類型。這種函數(類)也稱類屬函數(類)
。類型參數化多態的應用較廣泛,被稱為最純的多態。
重載是指用同壹個名字命名不同的函數或操作符。函數重載是C++對壹般程序設計語言中操作符重
載機制的擴充,它可使具有相同或相近含義的函數用相同的名字,只要其參數的個數、次序或類型不
壹樣即可。例如:
int min(int x,int y); //求2個整數的最小數
int min(int x,int y,int z); //求3個整數的最小數
int min(int n,int a〔〕); //求n個整數的最小數
當用戶要求增加比較2個字符串大小的功能時,只需增加:
char*min(char*,char*);
而原來如何使用這組函數的邏輯不需改變,min的功能擴充很容易,也就是說維護比較容易,同時也提
高了程序的可理解性,“min”表示求最小值的函數。
強制是指將壹種類型的值轉換成另壹種類型的值進行的語義操作,從而防止類型錯誤。類型轉換
可以是隱式的,在編譯時完成,如語句D=I把整型變量轉換為實型;也可以是顯式的,可在動態運行
時完成。
從總體上來說,壹般多態性是真正的多態性;特殊多態性只是表面的多態性。因為重載只允許某
壹個符號有多種類型,而它所代表的值分別具有不同的、不相兼容的類型。類似地,隱式類型轉換也
不是真正的多態,因為在操作開始前,各值必須轉換為要求的類型,而輸出類型也與輸入類型無關。
相比之下,子類與繼承卻是真正的多態。類型參數化多態也是壹種純正的多態,同壹對象或函數在不
同的類型上下文中統壹地使用而不需采用隱式類型轉換、運行時檢測或其它各種限制。
2 多態性應用
2.1 包含多態
C++中采用虛擬函數實現包含多態,虛擬函數為C++提供了更為靈活的多態機制,這種多態性在程
序運行時才能確定,因此虛擬函數是多態性的精華,至少含有壹個虛擬函數的類稱為多態類。包含多
態在程序設計中使用十分頻繁。
派生類繼承基類的所有操作,或者說,基類的操作能被用於操作派生類的對象,當基類的操作不
能適應派生類時,派生類需重載基類的操作,見下例中的void circle::showarea()。
#include <iostream.h>
class point //屏幕上的點類
{int x,y;public;
point(int x1,int y1)
{x=x1;y=y1;}
void showarea()
{cout<<〃Area of point is:〃<<0.0<<endl;}
};
class circle:public point//圓類
{int radius;
public:
circle(int x,int y,int r):point(x,y){ radius=r;}
void showarea(){cout<<〃Area of circle is:〃<<3.14
*radius*radius<<endl;}
};
void disparea(const point*p) //多態程序段
{p->showarea();}
void main()
{circle c1(1,1,1);disparea(&c1);
}
程序的運行結果為0.0(正確結果應為3.14),出錯的原因是:表達式p->showarea()中的函數調
用在編譯時被束定到函數體上,使得這個表達式中的函數調用執行point類的showarea()。為此,當程
序員在實現壹個派生類而變動了基類中的操作實現時,C++提供的虛函數機制可將這種變動告訴編譯器
,即將關鍵字virtual放在類point中該函數的函數說明之前(virtual void showarea()),程序其它部
分保持不變(circle::showarea()自動地成為虛函數),編譯器就不會對函數調用p->showarea()進
行靜態束定(在編譯/連接時進行的束定)而產生有關的代碼,使函數調用與它所應執行的代碼的束定
工作在程序運行時進行,這樣上述程序的運行結果即為3.14。在程序運行時進行的束定被稱為動態束
定。
利用虛函數,可在基類和派生類中使用相同的函數名定義函數的不同實現,從而實現“壹個接口
,多種方式”。當用基類指針或引用對虛函數進行訪問時,軟件系統將根據運行時指針或引用所指向
或引用的實際對象來確定調用對象所在類的虛函數版本。
C++語言還增加了純的虛函數機制用來更好地設計包含多態性。對於如圖1(a)所示結構的類層次,
假如每個類中都有壹個函數“void display(void);”,那麽,怎樣對它們按多態性進行統壹處理呢
?對這類問題應先設計壹個抽象的類,使它成為所有類的祖先類,如圖1(b)所示。設置類A的目的是由
它說明統壹使用該層次中的display()函數的方法(賦值兼容規則從語法上保證了A的子孫類可按A說明
的方式使用display()函數;多態性則從語義上保證了在執行時,根據實際的對象訪問相應對象類中的
display()函數)。
為了保證在類A中設置的display()函數是抽象動作,並能說明類A是壹個抽象的類,在C++中,可用純
的虛函數語言機制在類A中聲明1個成員函數“virtual void display(void)=0;”。請註意,在類A
的子孫類中要麽給出display()的定義,要麽重新將該函數聲明為純的。
從上面的分析可以看出,類A的設計盡管是用繼承性語法表達的,但它的主要目的不是為代碼***享而設
計的,而是為了提高多態性而設計的,它是另壹個維度的抽象。
2.2 類型參數化多態
參數化多態又稱非受限類屬多態,即將類型作為函數或類的參數,避免了為各種不同的數據類型
編寫不同的函數或類,減輕了設計者負擔,提高了程序設計的靈活性。
模板是C++實現參數化多態性的工具,分為函數模板和類模板二種。
類模板中的成員函數均為函數模板,因此函數模板是為類模板服務的。類模板在表示數組、表、
矩陣等類數據結構時,顯得特別重要,因為這些數據結構的表示和算法的選擇不受其所包含的元素的
類型的影響。下面是壹個通用數組類模板的定義。
template <class T,int N>
class array
{T elem〔N〕;
public:
array(){for(int j=0;j<N;j++)elem〔j〕=0;}
T& operator〔〕(int index){return elem〔index〕;}
void modi(int index,T value){elem〔index〕=value;}
};
其中,T是類型參數,N是常量參數。T和N的實際值是在生成具體類實例時指定的。類模板的< >
可以包括任意個類型參數或常量參數,但至少應有壹個參數。在類模板定義中,可在程序中通常使用
類型指定的任何地方使用類型參數,可在通常使用特定類型常量表達式的任何地方使用常量參數。
成員函數模板可放在類模板中定義,也可放在類外定義,例如:
template <class T,int N>
T& array<T,N>::operator〔〕(int index){return elem〔index〕;}
當由類模板生成壹個特定的類時,必須指定參數所代表的類型(值)。例如,1個元素類型為int、
長度為100的數組類使用類型表達式array<int,100>來表示,這個類型表達式被用於說明數組類對
象。例如:
array<int,100> a: //生成特定類的對象a
a.modi(1,34); //對象a訪問成員函數
類模板壹旦生成了對象和指定了參數表中的類型,編譯器在以後訪問數據成員和調用成員函數時
完全強制為這些類型。
在C++中可以重載定義多個同名的函數模板,也可以將1個函數模板與1個同名函數進行重載定義。
例如:
template <class T> T min(T a,T b){return a<b?a:b;}
template <class T>
T min(T a,T b,T c){T x=min(a,b);return min(x,c);}
int min(int a,int b)〔return a<b?a:b;}
調用min(3,7),則調用第3個函數;調用min(3.8.5.9),編譯器將根據帶2個參數的模板生成新函
數min(double,double);調用min(4,90,76),則編譯器根據帶3個參數的模板生成新函數min(int,
int,int);而調用min(56.3,48,71),編譯將給出錯誤信息,說明無法從上面的模板中生成函數
min(double,double,double),因為編譯器在類型推導時,不存在類型強制。
模板描述了1組函數或1組類,它主要用於避免程序員進行重復的編碼工作,大大簡化、方便了面
向對象的程序設計。
2.3 重載多態
重載是多態性的最簡形式,而且把更大的靈活性和擴展性添加到程序設計語言中,它分成操作符
重載和函數重載。
C++允許為類重定義已有操作符的語義,使系統預定義的操作符可操作類對象。C++語言的壹個非
常有說服力的例子是count對象的插入操作(<<)。由於其類中定義了對位左移操作符“<<”進行重
載的函數,使C++的輸出可按同壹種方式進行,學習起來非常容易。並且,增加壹個使其能輸出復數類
的功能(擴充)也很簡單,不必破壞原輸出邏輯。
C++規定將操作符重載為函數的形式,既可以重載為類的成員函數,也可以重載為類的友員函數。
用友員重載操作符的函數也稱操作符函數,它與用成員函數重載操作符的函數不同,後者本身是類中
成員函數,而它是類的友員函數,是獨立於類的壹般函數。註意重載操作符時,不能改變它們的優先
級,不能改變這些操作符所需操作數的個數。
重定義已有的函數稱為函數重載。在C++中既允許重載壹般函數,也允許重載類的成員函數。如對
構造函數進行重載定義,可使程序有幾種不同的途徑對類對象進行初始化。還允許派生類的成員函數
重載基類的成員函數,虛函數就屬於這種形式的重載,但它是壹種動態的重載方式,即所謂的“動態
聯編(束定)”。
2.4 強制多態
強制也稱類型轉換。C++語言定義了基本數據類型之間的轉換規則,即:
char->short->int->unsigned->long->unsigned long->float->double->long
double
賦值操作是個特例,上述原則不再適用。當賦值操作符的右操作數的類型與左操作數的類型不同
時,右操作數的值被轉換為左操作數的類型的值,然後將轉換後的值賦值給左操作數。
程序員可以在表達式中使用3種強制類型轉換表達式:①static_cast<T>(E);②T(E);③(T)E
。其中任意壹種都可改變編譯器所使用的規則,以便按自己的意願進行所需的類型強制。其中E 代表
壹個運算表達式,T代表壹個類型表達式。第三種表達形式是C語言中所使用的風格,在C++中,建議不
要再使用這種形式,應選擇使用第壹種形式。例如,設對象f的類型為double,且其值為3.14。則表達
式static_cast<int>(f)的值為3,類型為int。
通過構造函數進行類類型與其它數據類型之間的轉換必須有壹個前提,那就是此類壹定要有壹個
只帶1個非缺省參數的構造函數,通過構造函數進行類類型的轉換只能從參數類型向類類型轉換,而想
將壹個類類型向其它類型轉換是辦不到的。類類型轉換函數就是專門用來將類類型向其它本類型轉換
的,它是壹種類似顯式類型轉換的機制。轉換函數的設計有以下幾點要特別註意:①轉換函數必須是
類的成員函數;②轉換函數不可以指定其返回值類型;③轉換函數其參數行不可以有任何參數。
強制使類型檢查復雜化,尤其在允許重載的情況下,導致無法消解的二義性,在程序設計時要註
意避免由於強制帶來的二義性。