詳解C++編程的多態(tài)性概念
多態(tài)性(polymorphism)是面向?qū)ο蟪绦蛟O(shè)計(jì)的一個(gè)重要特征。如果一種語(yǔ)言只支持類(lèi)而不支持多態(tài),是不能被稱(chēng)為面向?qū)ο笳Z(yǔ)言的,只能說(shuō)是基于對(duì)象的,如Ada、VB就屬此類(lèi)。C++支持多態(tài)性,在C++程序設(shè)計(jì)中能夠?qū)崿F(xiàn)多態(tài)性。利用多態(tài)性可以設(shè)計(jì)和實(shí)現(xiàn)一個(gè)易于擴(kuò)展的系統(tǒng)。
顧名思義,多態(tài)的意思是一個(gè)事物有多種形態(tài)。多態(tài)性的英文單詞polymorphism來(lái)源于希臘詞根poly(意為“很多”)和morph(意為“形態(tài)”)。在C ++程序設(shè)計(jì)中,多態(tài)性是指具有不同功能的函數(shù)可以用同一個(gè)函數(shù)名,這樣就可以用一個(gè)函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。在面向?qū)ο蠓椒ㄖ幸话闶沁@樣表述多態(tài)性的:向不同的對(duì)象發(fā)送同一個(gè)消息, 不同的對(duì)象在接收時(shí)會(huì)產(chǎn)生不同的行為(即方法)。也就是說(shuō),每個(gè)對(duì)象可以用自己的方式去響應(yīng)共同的消息。所謂消息,就是調(diào)用函數(shù),不同的行為就是指不同的實(shí)現(xiàn),即執(zhí)行不同的函數(shù)。
其實(shí),我們已經(jīng)多次接觸過(guò)多態(tài)性的現(xiàn)象,例如函數(shù)的重載、運(yùn)算符重載都是多態(tài)現(xiàn)象。只是那時(shí)沒(méi)有用到多態(tài)性這一專(zhuān)門(mén)術(shù)語(yǔ)而已。例如,使用運(yùn)算符“+”使兩個(gè)數(shù)值相加,就是發(fā)送一個(gè)消息,它要調(diào)用operator +函數(shù)。實(shí)際上,整型、單精度型、雙精度型的加法操作過(guò)程是互不相同的,是由不同內(nèi)容的函數(shù)實(shí)現(xiàn)的。顯然,它們以不同的行為或方法來(lái)響應(yīng)同一消息。
在現(xiàn)實(shí)生活中可以看到許多多態(tài)性的例子。如學(xué)校校長(zhǎng)向社會(huì)發(fā)布一個(gè)消息:9月1日新學(xué)年開(kāi)學(xué)。不同的對(duì)象會(huì)作出不同的響應(yīng):學(xué)生要準(zhǔn)備好課本準(zhǔn)時(shí)到校上課;家長(zhǎng)要籌集學(xué)費(fèi);教師要備好課;后勤部門(mén)要準(zhǔn)備好教室、宿舍和食堂……由于事先對(duì)各種人的任務(wù)已作了規(guī)定,因此,在得到同一個(gè)消息時(shí),各種人都知道自己應(yīng)當(dāng)怎么做,這就是 多態(tài)性??梢栽O(shè)想,如果不利用多態(tài)性,那么校長(zhǎng)就要分別給學(xué)生、家長(zhǎng)、教師、后勤部門(mén)等許多不同的對(duì)象分別發(fā)通知,分別具體規(guī)定每一種人接到通知后應(yīng)該怎么做。顯然這是一件十分復(fù)雜而細(xì)致的工作。一人包攬一切,吃力還不討好?,F(xiàn)在,利用了多態(tài)性機(jī)制,校長(zhǎng)在發(fā)布消息時(shí),不必一一具體考慮不同類(lèi)型人員是怎樣執(zhí)行的。至于各類(lèi)人員在接到消息后應(yīng)氣做什么,并不是臨時(shí)決定的,而是學(xué)校的工作機(jī)制事先安排決定好的。校長(zhǎng)只需不斷發(fā)布各種消息,各種人員就會(huì)按預(yù)定方案有條不紊地工作。
同樣,在C++程序設(shè)計(jì)中,在不同的類(lèi)中定義了其響應(yīng)消息的方法,那么使用這些類(lèi) 時(shí),不必考慮它們是什么類(lèi)型,只要發(fā)布消息即可。正如在使用運(yùn)算符“ ”時(shí)不必考慮相加的數(shù)值是整型、單精度型還是雙精度型,直接使用“+”,不論哪類(lèi)數(shù)值都能實(shí)現(xiàn)相加??梢哉f(shuō)這是以不變應(yīng)萬(wàn)變的方法,不論對(duì)象千變?nèi)f化,用戶(hù)都是用同一形式的信息去調(diào)用它們,使它們根據(jù)事先的安排作出反應(yīng)。
從系統(tǒng)實(shí)現(xiàn)的角度看,多態(tài)性分為兩類(lèi):靜態(tài)多態(tài)性和動(dòng)態(tài)多態(tài)性。以前學(xué)過(guò)的函數(shù)重載和運(yùn)算符重載實(shí)現(xiàn)的多態(tài)性屬于靜態(tài)多態(tài)性,在程序編譯時(shí)系統(tǒng)就能決定調(diào)用的是哪個(gè)函數(shù),因此靜態(tài)多態(tài)性又稱(chēng)編譯時(shí)的多態(tài)性。靜態(tài)多態(tài)性是通過(guò)函數(shù)的重載實(shí)現(xiàn)的(運(yùn)算符重載實(shí)質(zhì)上也是函數(shù)重載)。動(dòng)態(tài)多態(tài)性是在程序運(yùn)行過(guò)程中才動(dòng)態(tài)地確定操作所針對(duì)的對(duì)象。它又稱(chēng)運(yùn)行時(shí)的多態(tài)性。動(dòng)態(tài)多態(tài)性是通過(guò)虛函數(shù)(Virtual fiinction)實(shí)現(xiàn)的。
下面是一個(gè)承上啟下的例子。一方面它是有關(guān)繼承和運(yùn)算符重載內(nèi)容的綜合應(yīng)用的例子,通過(guò)這個(gè)例子可以進(jìn)一步融會(huì)貫通前面所學(xué)的內(nèi)容,另一方面又是作為討論多態(tài)性的一個(gè)基礎(chǔ)用例。
希望大家耐心、深入地閱讀和消化這個(gè)程序,弄清其中的每一個(gè)細(xì)節(jié)。
[例] 先建立一個(gè)Point(點(diǎn))類(lèi),包含數(shù)據(jù)成員x,y(坐標(biāo)點(diǎn))。以它為基類(lèi),派生出一個(gè)Circle(圓)類(lèi),增加數(shù)據(jù)成員r(半徑),再以Circle類(lèi)為直接基類(lèi),派生出一個(gè)Cylinder(圓柱體)類(lèi),再增加數(shù)據(jù)成員h(高)。要求編寫(xiě)程序,重載運(yùn)算符“<<”和“>>”,使之能用于輸出以上類(lèi)對(duì)象。
這個(gè)例題難度不大,但程序很長(zhǎng)。對(duì)于一個(gè)比較大的程序,應(yīng)當(dāng)分成若干步驟進(jìn)行。先聲明基類(lèi),再聲明派生類(lèi),逐級(jí)進(jìn)行,分步調(diào)試。
1) 聲明基類(lèi)Point
類(lèi)可寫(xiě)出聲明基類(lèi)Point的部分如下:
#include <iostream> //聲明類(lèi)Point class Point { public: Point(float x=0,float y=0); //有默認(rèn)參數(shù)的構(gòu)造函數(shù) void setPoint(float ,float); //設(shè)置坐標(biāo)值 float getX( )const {return x;} //讀x坐標(biāo) float getY( )const {return y;} //讀y坐標(biāo) friend ostream & operator <<(ostream &,const Point &); //重載運(yùn)算符“<<” protected: //受保護(hù)成員 float x, y; }; //下面定義Point類(lèi)的成員函數(shù) Point::Point(float a,float b) //Point的構(gòu)造函數(shù) { //對(duì)x,y初始化 x=a; y=b; } void Point::setPoint(float a,float b) //設(shè)置x和y的坐標(biāo)值 { //為x,y賦新值 x=a; y=b; } //重載運(yùn)算符“<<”,使之能輸出點(diǎn)的坐標(biāo) ostream & operator <<(ostream &output, const Point &p) { output<<"["<<p.x<<","<<p.y<<"]"<<endl; return output; }
以上完成了基類(lèi)Point類(lèi)的聲明。
為了提高程序調(diào)試的效率,提倡對(duì)程序分步調(diào)試,不要將一個(gè)長(zhǎng)的程序都寫(xiě)完以后才統(tǒng)一調(diào)試,那樣在編譯時(shí)可能會(huì)同時(shí)出現(xiàn)大量的編譯錯(cuò)誤,面對(duì)一個(gè)長(zhǎng)的程序,程序人員往往難以迅速準(zhǔn)確地找到出錯(cuò)位置。要善于將一個(gè)大的程序分解為若干個(gè)文件,分別編譯,或者分步調(diào)試,先通過(guò)最基本的部分,再逐步擴(kuò)充。
現(xiàn)在要對(duì)上面寫(xiě)的基類(lèi)聲明進(jìn)行調(diào)試,檢查它是否有錯(cuò),為此要寫(xiě)出main函數(shù)。實(shí)際上它是一個(gè)測(cè)試程序。
int main( ) { Point p(3.5,6.4); //建立Point類(lèi)對(duì)象p cout<<"x="<<p.getX( )<<",y="<<p.getY( )<<endl; //輸出p的坐標(biāo)值 p.setPoint(8.5,6.8); //重新設(shè)置p的坐標(biāo)值 cout<<"p(new):"<<p<<endl; //用重載運(yùn)算符“<<”輸出p點(diǎn)坐標(biāo) return 0; }
getX和getY函數(shù)聲明為常成員函數(shù),作用是只允許函數(shù)引用類(lèi)中的數(shù)據(jù),而不允許修改它們,以保證類(lèi)中數(shù)據(jù)的安全。數(shù)據(jù)成員x和y聲明為protected,這樣可以被派生類(lèi)訪(fǎng)問(wèn)(如果聲明為private,派生類(lèi)是不能訪(fǎng)問(wèn)的)。
程序編譯通過(guò),運(yùn)行結(jié)果為:
x=3.5,y=6.4 p(new):[8.5,6.8]
測(cè)試程序檢查了基類(lèi)中各函數(shù)的功能,以及運(yùn)算符重載的作用,證明程序是正確的。
2)聲明派生類(lèi)Circle
在上面的基礎(chǔ)上,再寫(xiě)出聲明派生類(lèi)Circle的部分:
class Circle:public Point //circle是Point類(lèi)的公用派生類(lèi) { public: Circle(float x=0,float y=0,float r=0); //構(gòu)造函數(shù) void setRadius(float ); //設(shè)置半徑值 float getRadius( )const; //讀取半徑值 float area ( )const; //計(jì)算圓面積 friend ostream &operator <<(ostream &,const Circle &); //重載運(yùn)算符“<<” private: float radius; }; //定義構(gòu)造函數(shù),對(duì)圓心坐標(biāo)和半徑初始化 Circle::Circle(float a,float b,float r):Point(a,b),radius(r){} //設(shè)置半徑值 void Circle::setRadius(float r){radius=r;} //讀取半徑值 float Circle::getRadius( )const {return radius;} //計(jì)算圓面積 float Circle::area( )const { return 3.14159*radius*radius; } //重載運(yùn)算符“<<”,使之按規(guī)定的形式輸出圓的信息 ostream &operator <<(ostream &output,const Circle &c) { output<<"Center=["<<c.x<<","<<c.y<<"],r="<<c.radius<<",area="<<c.area( )<<endl; return output; }
為了測(cè)試以上Circle類(lèi)的定義,可以寫(xiě)出下面的主函數(shù):
int main( ) { Circle c(3.5,6.4,5.2); //建立Circle類(lèi)對(duì)象c,并給定圓心坐標(biāo)和半徑 cout<<"original circle:\\nx="<<c.getX()<<", y="<<c.getY()<<", r="<<c.getRadius( )<<", area="<<c.area( )<<endl; //輸出圓心坐標(biāo)、半徑和面積 c.setRadius(7.5); //設(shè)置半徑值 c.setPoint(5,5); //設(shè)置圓心坐標(biāo)值x,y cout<<"new circle:\\n"<<c; //用重載運(yùn)算符“<<”輸出圓對(duì)象的信息 Point &pRef=c; //pRef是Point類(lèi)的引用變量,被c初始化 cout<<"pRef:"<<pRef; //輸出pRef的信息 return 0; }
程序編譯通過(guò),運(yùn)行結(jié)果為:
original circle:(輸出原來(lái)的圓的數(shù)據(jù)) x=3.5, y=6.4, r=5.2, area=84.9486 new circle:(輸出修改后的圓的數(shù)據(jù)) Center=[5,5], r=7.5, area=176.714 pRef:[5,5] (輸出圓的圓心“點(diǎn)”的數(shù)據(jù))
可以看到,在Point類(lèi)中聲明了一次運(yùn)算符“ <<”重載函數(shù),在Circle類(lèi)中又聲明了一次運(yùn)算符“ <<”,兩次重載的運(yùn)算符“<<”內(nèi)容是不同的,在編譯時(shí)編譯系統(tǒng)會(huì)根據(jù)輸出項(xiàng)的類(lèi)型確定調(diào)用哪一個(gè)運(yùn)算符重載函數(shù)。main函數(shù)第7行用“cout<< ”輸出c,調(diào)用的是在Circle類(lèi)中聲明的運(yùn)算符重載函數(shù)。
請(qǐng)注意main函數(shù)第8行:
Point & pRef = c;
定義了 Point類(lèi)的引用變量pRef,并用派生類(lèi)Circle對(duì)象c對(duì)其初始化。前面我們已經(jīng)講過(guò),派生類(lèi)對(duì)象可以替代基類(lèi)對(duì)象為基類(lèi)對(duì)象的引用初始化或賦值(詳情請(qǐng)查看:C++基類(lèi)與派生類(lèi)的轉(zhuǎn)換)?,F(xiàn)在 Circle是Point的公用派生類(lèi),因此,pRef不能認(rèn)為是c的別名,它得到了c的起始地址, 它只是c中基類(lèi)部分的別名,與c中基類(lèi)部分共享同一段存儲(chǔ)單元。所以用“cout<<pRef”輸出時(shí),調(diào)用的不是在Circle中聲明的運(yùn)算符重載函數(shù),而是在Point中聲明的運(yùn)算符重載函數(shù),輸出的是“點(diǎn)”的信息,而不是“圓”的信息。
3) 聲明Circle的派生類(lèi)Cylinder
前面已從基類(lèi)Point派生出Circle類(lèi),現(xiàn)在再?gòu)腃ircle派生出Cylinder類(lèi)。
class Cylinder:public Circle// Cylinder是Circle的公用派生類(lèi) { public: Cylinder (float x=0,float y=0,float r=0,float h=0); //構(gòu)造函數(shù) void setHeight(float ); //設(shè)置圓柱高 float getHeight( )const; //讀取圓柱高 loat area( )const; //計(jì)算圓表面積 float volume( )const; //計(jì)算圓柱體積 friend ostream& operator <<(ostream&,const Cylinder&); //重載運(yùn)算符<< protected: float height;//圓柱高 }; //定義構(gòu)造函數(shù) Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){} //設(shè)置圓柱高 void Cylinder::setHeight(float h){height=h;} //讀取圓柱高 float Cylinder::getHeight( )const {return height;} //計(jì)算圓表面積 float Cylinder::area( )const { return 2*Circle::area( )+2*3.14159*radius*height;} //計(jì)算圓柱體積 float Cylinder::volume()const {return Circle::area()*height;} ostream &operator <<(ostream &output,const Cylinder& cy) { output<<"Center=["<<cy.x<<","<<cy.y<<"],r="<<cy.radius<<",h="<<cy.height <<"\\narea="<<cy.area( )<<", volume="<<cy.volume( )<<endl; return output; } //重載運(yùn)算符“<<”
可以寫(xiě)出下面的主函數(shù):
int main( ) { Cylinder cy1(3.5,6.4,5.2,10);//定義Cylinder類(lèi)對(duì)象cy1 cout<<"\\noriginal cylinder:\\nx="<<cy1.getX( )<<", y="<<cy1.getY( )<<", r=" <<cy1.getRadius( )<<", h="<<cy1.getHeight( )<<"\\narea="<<cy1.area() <<",volume="<<cy1.volume()<<endl;//用系統(tǒng)定義的運(yùn)算符“<<”輸出cy1的數(shù)據(jù) cy1.setHeight(15);//設(shè)置圓柱高 cy1.setRadius(7.5);//設(shè)置圓半徑 cy1.setPoint(5,5);//設(shè)置圓心坐標(biāo)值x,y cout<<"\\nnew cylinder:\\n"<<cy1;//用重載運(yùn)算符“<<”輸出cy1的數(shù)據(jù) Point &pRef=cy1;//pRef是Point類(lèi)對(duì)象的引用變量 cout<<"\\npRef as a Point:"<<pRef;//pRef作為一個(gè)“點(diǎn)”輸出 Circle &cRef=cy1;//cRef是Circle類(lèi)對(duì)象的引用變量 cout<<"\\ncRef as a Circle:"<<cRef;//cRef作為一個(gè)“圓”輸出 return 0; }
運(yùn)行結(jié)果如下:
original cylinder:(輸出cy1的初始值) x=3.5, y=6.4, r=5.2, h=10 (圓心坐標(biāo)x,y。半徑r,高h(yuǎn)) area=496.623, volume=849.486 (圓柱表面積area和體積volume) new cylinder: (輸出cy1的新值) Center=[5,5], r=7.5, h=15 (以[5,5]形式輸出圓心坐標(biāo)) area=1060.29, volume=2650.72(圓柱表面積area和體積volume) pRef as a Point:[5,5] (pRef作為一個(gè)“點(diǎn)”輸出) cRef as a Circle:Center=[5,5], r=7.5, area=176.714(cRef作為一個(gè)“圓”輸出)
說(shuō)明:在Cylinder類(lèi)中定義了 area函數(shù),它與Circle類(lèi)中的area函數(shù)同名,根據(jù)前面我們講解的同名覆蓋的原則(詳情請(qǐng)查看:C++多重繼承的二義性問(wèn)題),cy1.area( ) 調(diào)用的是Cylinder類(lèi)的area函數(shù)(求圓柱表面積),而不是Circle類(lèi)的area函數(shù)(圓面積)。請(qǐng)注意,這兩個(gè)area函數(shù)不是重載函數(shù),它們不僅函數(shù)名相同,而且函數(shù)類(lèi)型和參數(shù)個(gè)數(shù)都相同,兩個(gè)同名函數(shù)不在同 —個(gè)類(lèi)中,而是分別在基類(lèi)和派生類(lèi)中,屬于同名覆蓋。重載函數(shù)的參數(shù)個(gè)數(shù)和參數(shù)類(lèi)型必須至少有一者不同,否則系統(tǒng)無(wú)法確定調(diào)用哪一個(gè)函數(shù)。
main函數(shù)第9行用“cout<<cy1”來(lái)輸出cy1,此時(shí)調(diào)用的是在Cylinder類(lèi)中聲明的重載運(yùn)算符“<<”,按在重載時(shí)規(guī)定的方式輸出圓柱體cy1的有關(guān)數(shù)據(jù)。
main函數(shù)中最后4行的含義與在定義Circle類(lèi)時(shí)的情況類(lèi)似。pRef是Point類(lèi)的引用變量,用cy1對(duì)其初始化,但它不是cy1的別名,只是cy1中基類(lèi)Point部分的別名,在輸出pRef時(shí)是作為一個(gè)Point類(lèi)對(duì)象輸出的,也就是說(shuō),它是一個(gè)“點(diǎn)”。同樣,cRef是Circle類(lèi)的引用變量,用cy1對(duì)其初始化,但它只是cy1中的直接基類(lèi)Circle部分的別名, 在輸出 cRef 時(shí)是作為Circle類(lèi)對(duì)象輸出的,它是一個(gè)"圓”,而不是一個(gè)“圓柱體”。從輸 出的結(jié)果可以看出調(diào)用的是哪個(gè)運(yùn)算符函數(shù)。
在本例中存在靜態(tài)多態(tài)性,這是運(yùn)算符重載引起的(注意3個(gè)運(yùn)算符函數(shù)是重載而不是同名覆蓋,因?yàn)橛幸粋€(gè)形參類(lèi)型不同)??梢钥吹?,在編譯時(shí)編譯系統(tǒng)即可以判定應(yīng)調(diào)用哪個(gè)重載運(yùn)算符函數(shù)。
相關(guān)文章
C語(yǔ)言關(guān)于時(shí)間復(fù)雜度詳解
大家好,本篇文章主要講的是C語(yǔ)言關(guān)于時(shí)間復(fù)雜度詳解,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽2022-01-01C++?std::array實(shí)現(xiàn)編譯器排序
這篇文章主要介紹了C++?std::array實(shí)現(xiàn)編譯器排序,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03