第九章0522程序設計課件_第1頁
第九章0522程序設計課件_第2頁
第九章0522程序設計課件_第3頁
第九章0522程序設計課件_第4頁
第九章0522程序設計課件_第5頁
已閱讀5頁,還剩64頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第九章鄭偉第九章多態(tài)9.1多態(tài)性的概念9.2重載、覆蓋與靜態(tài)聯(lián)編9.3虛函數(shù)與運行時多態(tài)9.4純虛函數(shù)與抽象類9.5模板9.1多態(tài)性的概念 多態(tài)性(polymorphism)是面向?qū)ο蟪绦蛟O計的重要特征。C++支持多態(tài)性,利用多態(tài)性,可以設計和擴展一個易于擴展的系統(tǒng)。什么叫多態(tài)?多態(tài)的意思是一種事物的多種形態(tài)。在C++中,是指具有不同功能的函數(shù)可以用同一個函數(shù)名。面向?qū)ο蠓椒ㄖ幸话闶沁@樣描述多態(tài)性的:向不同的對象發(fā)送同一個消息,不同的對象在接收時會產(chǎn)生不同的行為(即方法)。 9.1.1面向?qū)ο蟪绦蛟O計中的多態(tài) 我們其實已經(jīng)接觸過多態(tài)性的現(xiàn)象。如函數(shù)的重載,運算符的重載。多態(tài)性分類:從系統(tǒng)實現(xiàn)的角度看,多態(tài)性分為以下兩類:靜態(tài)多態(tài)性:又稱編譯時的多態(tài)性。如函數(shù)重載和運算符重載,都屬于靜態(tài)多態(tài)性。動態(tài)多態(tài)性:有稱為運行時的多態(tài)性。它主要表現(xiàn)為虛函數(shù)(virtualfunction)。9.1.1面向?qū)ο蟪绦蛟O計中的多態(tài)面向?qū)ο蟮亩鄳B(tài)可以分為四類:(1)重載多態(tài)(2)強制多態(tài)(3)包含多態(tài)(或運行(時)多態(tài))(4)參數(shù)多態(tài)普通函數(shù)及類的成員函數(shù)重載、運算符重載屬于重載多態(tài)類型強制轉(zhuǎn)換屬于強制多態(tài)虛函數(shù)屬于包含多態(tài)函數(shù)模板和類模板屬于參數(shù)多態(tài)9.1.2多態(tài)性的實現(xiàn)——聯(lián)編主程序在調(diào)用子程序之前,一般要:保存現(xiàn)場→拿到子程序的地址→轉(zhuǎn)向子程序執(zhí)行。在C++中,普通的函數(shù)調(diào)用由C++鏈接器在鏈接過程中將子程序(被調(diào)函數(shù))的內(nèi)存(邏輯)地址放到主程序(主調(diào)函數(shù))的代碼中。如果主程序調(diào)用子程序的語句是多態(tài)性語句,那么在執(zhí)行調(diào)用之前,主程序必須得確定子程序的地址(或者說,確定調(diào)用哪個子程序),這個過程稱為聯(lián)編(Binding),也可翻譯成“綁定”。9.1.2多態(tài)性的實現(xiàn)——聯(lián)編聯(lián)編有兩種方式:靜態(tài)聯(lián)編和動態(tài)聯(lián)編。(1)在源程序編譯的時候就能確定具有多態(tài)性的語句調(diào)用哪個函數(shù),稱為靜態(tài)聯(lián)編。函數(shù)重載和模板都是在編譯時確定被調(diào)函數(shù),所以屬于靜態(tài)聯(lián)編。(2)在程序運行時才能夠確定具有多態(tài)性的語句究竟調(diào)用哪個函數(shù),稱為動態(tài)聯(lián)編。用動態(tài)聯(lián)編實現(xiàn)的多態(tài),也稱為運行時多態(tài)(RuntimePolymorphism)。聯(lián)編與多態(tài)之間的關(guān)系可以用圖9-1簡明地描述。9.2重載、覆蓋與靜態(tài)聯(lián)編9.2.1重載與靜態(tài)聯(lián)編以下是一個重載函數(shù)的例子。intadd(inta){returna+10;}intadd(inta,intb){returna+b;}intmain(){intx=1,y=2;add(x);add(x,y);return0;上述代碼中有兩個重載函數(shù)add(),在main()函數(shù)中調(diào)用了這兩個重載函數(shù)。編譯器根據(jù)函數(shù)的參數(shù)能夠確定每次調(diào)用哪個函數(shù),而每個函數(shù)都有自己的地址,編譯器只要加上轉(zhuǎn)移到相應地址的“轉(zhuǎn)移指令”,就可以完成對不同重載函數(shù)的調(diào)用。由此可見,所謂的“同名”僅僅是C++的特性,經(jīng)過編譯器處理之后,同名的函數(shù)變成了不同地址的子程序,對同名函數(shù)的調(diào)用變成了對不同地址子程序的調(diào)用。這個轉(zhuǎn)變過程是由編譯器完成的,換句話說,編譯器分辨出了同名函數(shù)的不同之處。這屬于靜態(tài)聯(lián)編。9.2重載、覆蓋與靜態(tài)聯(lián)編9.2.1覆蓋與靜態(tài)聯(lián)編覆蓋現(xiàn)象只能出現(xiàn)在繼承樹中。在派生類中定義和基類中同名的成員函數(shù),是對基類進行改造,為派生類增加新行為的一種常用的方法。通過不同的派生類的對象(對象指針或者對象引用),調(diào)用這些同名的成員函數(shù),實現(xiàn)不同的操作,也是多態(tài)性的一種表現(xiàn)。在某些情況下,覆蓋會導致靜態(tài)聯(lián)編;而另外一些情況下,則會導致動態(tài)聯(lián)編。9.2重載、覆蓋與靜態(tài)聯(lián)編9.2.1覆蓋與靜態(tài)聯(lián)編例9-19-1用對象訪問繼承樹中的同名函數(shù)。//以下代碼僅為示例,無法編譯classB{public:f(){.}};//基類classP:publicB{public:f(){.}};//派生類1classQ:publicB{public:f(){.}};//派生類2main(){Pp;Qq;//創(chuàng)建派生類對象p.f();//調(diào)用P::f()q.f();//調(diào)用Q::f()}基類B派生出類P和類Q,這3個類中都有同名函數(shù)f()。在類的外部,通過派生類的對象調(diào)用同名函數(shù),那么該調(diào)用被編譯器在編譯階段鏈接到該對象所屬類的同名函數(shù)上。這種情況下,編譯器執(zhí)行了靜態(tài)聯(lián)編。通過對象調(diào)用類的同名函數(shù),一定是本類中的函數(shù)。9.2重載、覆蓋與靜態(tài)聯(lián)編9.2.1覆蓋與靜態(tài)聯(lián)編例9-29-2用指針訪問繼承樹中的同名函數(shù)。//以下代碼僅為示例,無法編譯classB{public:f(){.}};//基類classP:publicB{public:f(){.}};//派生類1classQ:publicB{public:f(){.}};//派生類2main(){B*b_ptr;Pp;Qq;//定義基類的指針和派生類的對象b_ptr=&p;//基類指針指向派生類b_ptr->f();//通過基類指針調(diào)用B::f()b_ptr=&q;//基類指針指向派生類b_ptr->f();//通過基類指針調(diào)用B::f()}基類B派生出類P和類Q,這3個類中都有同名函數(shù)f()。在類的外部,用派生類對象的地址為基類指針賦值,隨后,使用該基類指針調(diào)用同名函數(shù)。此時所調(diào)用的同名函數(shù)是基類的同名函數(shù)。原因在于,用派生類對象的地址為基類指針賦值時,實際上進行了派生類對象向基類對象的轉(zhuǎn)換(參見8.4.2小節(jié))。上述情況下,編譯器也執(zhí)行了靜態(tài)聯(lián)編。9.2.3一個典型的例子 現(xiàn)在來看一個綜合性的示例,請同學們認真讀懂這個程序。 我們先建立一個point(點)類,包含數(shù)據(jù)成員x,y(坐標點)。以它為基類,派生出一個circle(圓)類,增加數(shù)據(jù)成員r(半徑)。再以circle類為直接基類,派生出一個cylinder(圓柱體)類,增加數(shù)據(jù)成員h(高)。要求重載運算符“<<”和“>>”,使之能輸出這些對象。 這個題目程序較長,我們分為若干步驟進行,分步調(diào)試:先聲明基類point類,并調(diào)試之;再聲明派生類circle類,調(diào)試之;最后聲明cylinder類,調(diào)試之。①先聲明基類point#include<iostream>usingnamespacestd;classpoint//聲明類point{public:point(floatx=0,floaty=0);//有默認值的構(gòu)造函數(shù)voidsetPoint(float,float);//設置點坐標floatgetX()const{returnx;}//讀x值floatgetY()const{returny;}//讀y值

friendostream&operator<<(ostream&,constpoint&);protected:floatx,y;};//下面定義point類的成員函數(shù)point::point(floata,floatb)//point的構(gòu)造函數(shù){x=a;y=b;}voidpoint::setPoint(floata,floatb){x=a;y=b;}ostream&operator<<(ostream&output,constpoint&p){//重載運算符<<output<<“[x=“<<p.x<<“,y=“<<p.y<<“]”<<endl;returnoutput;}編寫好左邊程序后,可調(diào)試它的正確性。我們編寫一個臨時的main函數(shù):Voidmain(){pointp(1.1,2.2);cout<<p<<endl;p.setPoint(3.3,4.4);cout<<p<<endl;}

運行通過后,刪除臨時的main函數(shù),進入下一步工作。②再聲明派生類circle類classcircle:publicpoint//聲明類citcle{public:circle(floatx=0,floaty=0,floatr=0);//構(gòu)造函數(shù)voidsetRadius(float);//設置圓半徑floatgetRadius()const{returnradius;}//讀r值floatarea()const;//計算圓面積friendostream&operator<<(ostream&,constcircle&);protected:floatradius;};//下面定義circle類的成員函數(shù)circle::circle(floata,floatb,floatr)

:point(a,b),radius(r){}//point的構(gòu)造函數(shù)voidcircle::setRadius(floatr){radius=r;}floatcircle::area()const//計算圓面積{return3.14159*radius*radius;}ostream&operator<<(ostream&output,constcircle&c){//重載運算符<<output<<“[x=“<<c.x<<“,y=“<<c.y<<“],r=”<<c.radius<<“,area=“<<c.area()<<endl;returnoutput;}編寫好左邊程序后,可調(diào)試它的正確性。我們編寫一個臨時的main函數(shù):Voidmain(){circlec(1.1,2.2,1.0);c.setRadius(2.0);c.setPoint(2.0,2.0);cout<<c<<endl;}

運行通過后,刪除臨時的main函數(shù),進入下一步工作。③最后聲明cylinder類classcylinder:publiccircle//聲明類cylinder{public:cylinder(floatx=0,floaty=0,floatr=0,floath=0);//構(gòu)造函數(shù)voidsetHeight(floath){height=h;};//設置圓柱體高floatgetHeight()const{returnheight;}//讀r值floatarea()const;//計算圓柱表面積

floatvolume()const;//求體積friendostream&operator<<(ostream&,constcylinder&);protected:floatheight;};//下面定義cylinder類的成員函數(shù)cylinder::cylinder(floata,floatb,floatr,floath)

:circle(a,b,r),height(h){}//point的構(gòu)造函數(shù)floatcylinder::area()const//計算圓柱表面積{return2*circle::area()+2*3.14159*radius*height;}Floatcylinder::volume()const{returncircle::area()*height;}//求圓柱體積ostream&operator<<(ostream&output,constcylinder&cy){//重載運算符<<output<<“[x=“<<cy.x<<“,y=“<<cy.y<<“],r=”<<cy.radius<<“,height=“<<cy.height<<“,area=“<<cy.area()<<“,volume=“<<cy.volume()<<endl;returnoutput;}最后寫出主函數(shù)voidmain(){cylindercy1(3.0,6.0,2.0,10.0);cout<<“originalcylinder:\n“<<cy1;cy1.setHeight(15.0);cy1.setRadius(7.5);cy1.setPoint(5,5);cout<<“newcylinder:\n“<<cy1;point&pRef=cy1;cout<<“\npRefasapoint:”<<pRef;circle&cRef=cy1;cout<<“\ncRefasacircle:”<<cRef;}9.3虛函數(shù)與運行時多態(tài)

上節(jié)介紹的程序中,circle類和cylinder類都定義了area函數(shù),但功能不一樣。前者是求圓面積,后者是求圓柱體表面積。由于處在不同的類中,同名是合法的。 如果在圓柱體對象cy1中要調(diào)用求圓面積的函數(shù),則應該寫成:cy1.circle::area(),而不能寫成cy1.area()。用這種方法來區(qū)分兩種同名函數(shù)。使用起來不方便。 人們要問,能否用一個調(diào)用形式,既能調(diào)用派生類的函數(shù),又能調(diào)用基類同名函數(shù)?C++中的虛函數(shù)就是用來解決這一問題。虛函數(shù)的作用:虛函數(shù)的作用是允許在派生類中重新定義與基類同名的函數(shù),并且可以通過基類指針或引用來訪問基類和派生類中的同名函數(shù)。請看示例程序:示例#include<iostream>#include<string>usingnamespacestd;classstudent{public:student(intn,stringnam,floats){num=n;name=nam;score=s;}voiddisplay(){cout<<“num:”<<num<<“name:”<<name<<“score:”<<score<<endl;}protected:intnum;stringname;floatscore;};classgraduate:publicstudent{public:graduate(intn,stringnam,floats,floatp):student(n,nam,s),pay(p){}voiddisplay(){cout<<“num:”<<num<<“name:”<<name<<“score:”<<score<<“pay:”<<pay<<endl;}private:floatpay;};voidmain(){students1(1001,”Li”,98.5);graduateg1(2001,”Liu”,90.5,800.5);student*pt=&s1;pt->display();//指向基類對象s1pt=&g1;pt->display();//指向派生類對象g1,僅輸出了派生類的基類數(shù)據(jù)成員,因為它調(diào)用的是基類成員函數(shù)display!}假如想輸出派生類的全部數(shù)據(jù),當然可以采用下面兩種方法之一:通過派生類對象名g1,調(diào)用派生類對象的成員函數(shù):g1.display();定義一個指向派生類的指針ptr,并指向g1,然后用ptr->display()。虛函數(shù) 我們可以用虛函數(shù)可以順利解決這一問題。方法是:在基類student中聲明display函數(shù)時,在最左邊加上一個關(guān)鍵字virtual:

virtualvoiddisplay();

就可以student類的display函數(shù)聲明為虛函數(shù),程序其它部分不變編譯運行后,可見,使用pt->display(),的確將graduate類對象g1的全部數(shù)據(jù)顯示了出來,說明它調(diào)用的是g1的成員函數(shù)display。在派生類中重新定義該函數(shù),要求函數(shù)名、函數(shù)類型、參數(shù)表完全相同。只在類里的成員函數(shù)聲明時,加上關(guān)鍵字virtual,在類外定義定義虛函數(shù)時,不必加virtual關(guān)鍵字。定義一個指向基類對象的指針,并使她指向同一類族中的某一對象;通過該指針變量調(diào)用此虛函數(shù),此時調(diào)用的就是指針變量指向的對象的同名函數(shù)。虛函數(shù)示例 通過使用虛函數(shù)和指針,就能方便地調(diào)用同一類族中不同類對象的同名函數(shù),只要先用基類指針指向該對象即可。#include<iostream>#include<string>usingnamespacestd;classstudent{public:student(intn,stringnam,floats){num=n;name=nam;score=s;}

virtualvoiddisplay(){cout<<“num:”<<num<<“name:”<<name<<“score:”<<score<<endl;}protected:intnum;stringname;floatscore;};classgraduate:publicstudent{public:graduate(intn,stringnam,floats,floatp):student(n,nam,s),pay(p){}voiddisplay(){cout<<“num:”<<num<<“name:”<<name<<“score:”<<score<<“pay:”<<pay<<endl;}private:floatpay;};voidmain(){students1(1001,”Li”,98.5);graduateg1(2001,”Liu”,90.5,800.5);student*pt=&s1;pt->display();//指向基類對象s1pt=&g1;pt->display();//指向派生類對象g1,調(diào)用g1的顯示函數(shù)display,輸出g1全部數(shù)據(jù)成員}虛函數(shù)示例

例9-4用指針+虛函數(shù)的形式實現(xiàn)動態(tài)聯(lián)編。classB{public:virtualf(){}};classP:publicB{public:f(){}};classQ:publicB{public:f(){}};main(){B*b_ptr;Pp;Qq;b_ptr=&p;b_ptr->f();//調(diào)用的是P::f()b_ptr=&q;b_ptr->f();//調(diào)用的是Q::f()}例9-5用引用+虛函數(shù)的形式實現(xiàn)動態(tài)聯(lián)編。classB{public:virtualf(){}};classP:publicB{public:f(){}};classQ:publicB{public:f(){}};main(){Pp;Qq;B&b_ref1=p;b_ref.f();//調(diào)用的是P::f()B&b_ref2=q;b_ref2.f();//調(diào)用的是Q::f()} 將函數(shù)重載與虛函數(shù)比較,可見:函數(shù)重載是解決的是同一層次上的同名函數(shù)的問題。是橫向重載。虛函數(shù)解決的是不同派生層次上的同名函數(shù)的問題。相當于縱向重載。同一類族的虛函數(shù)的首部是相同的;而重載函數(shù)的首部不相同(參數(shù)表不能相同)。虛函數(shù)何時為靜態(tài)聯(lián)編?何時為動態(tài)聯(lián)編?靜態(tài)聯(lián)編與動態(tài)聯(lián)編

C++在編譯或運行時,對多個同名函數(shù)究竟調(diào)用哪一個函數(shù),需要一定的機制來確定。這種確定調(diào)用的具體對象的過程稱為“聯(lián)編(binding,也稱關(guān)聯(lián))”,即把函數(shù)名與某一個類對象捆綁在一起。

函數(shù)重載,在編譯時就可確定其調(diào)用的函數(shù)是哪一個;通過對象名調(diào)用虛函數(shù),在編譯時也可確定其調(diào)用的虛函數(shù)屬于哪一個類。其過程稱為“靜態(tài)關(guān)聯(lián)(staticbinding),因為是在運行前進行關(guān)聯(lián)的,又成為早期關(guān)聯(lián)(earlybinding)。 通過指針和虛函數(shù)的結(jié)合,在編譯階段是沒法進行關(guān)聯(lián)的,因為編譯只作靜態(tài)的語法檢查,光從語句形式pt->display()無法確定調(diào)用的對象,也就沒法關(guān)聯(lián)。 出現(xiàn)這種情況,我們可以在運行階段來處理關(guān)聯(lián)。在運行階段,基類指針先指向某一個對象,然后通過指針調(diào)用該對象的成員函數(shù)。此時調(diào)用哪個函數(shù)是確定的,沒有不確定因素。例如:pt=&g1;pt->display();

這種情況由于是在運行階段將虛函數(shù)與某一類對象“綁定”在一起的,因此稱為“動態(tài)關(guān)聯(lián)(dynamicbinding),或“滯后關(guān)聯(lián)(latebinding)”。使用虛函數(shù),要注意只能用virtual聲明類的成員函數(shù),類外的普通函數(shù)不能聲明成虛函數(shù),因為它沒有繼承的操作。一個成員函數(shù)被聲明成虛函數(shù)后,在同一類族中的類就不能再定義一個非virtual的、但與該函數(shù)具有相同參數(shù)表和返回類型的同名函數(shù)。使用虛函數(shù),系統(tǒng)要有一定的空間開銷。當一個類帶有虛函數(shù)時,編譯系統(tǒng)會為該類構(gòu)造一個虛函數(shù)表(virtualfunctiontable,vtable),它是一個指針數(shù)組,存放每個虛函數(shù)的入口地址。

什么情況下使用虛函數(shù)?成員函數(shù)所在的類是否會作為基類?成員函數(shù)被繼承后有沒有可能發(fā)生功能變化,如果兩個因素都具備,就應該將它聲明成虛函數(shù)。如果成員函數(shù)被繼承后功能不變,或派生類用不到該函數(shù),就不要聲明成虛函數(shù)。應考慮對成員函數(shù)的訪問是通過對象名還是基類指針,如果是通過基類指針或引用訪問,則應當聲明為虛函數(shù)。有時基類中定義虛函數(shù)時并不定義它的函數(shù)體,即函數(shù)體為空。其作用只是定義了一個虛函數(shù)名,具體功能留給派生類添加(純虛函數(shù))。虛析構(gòu)函數(shù)問題的引出:我們知道,當派生類對象撤消時,系統(tǒng)先調(diào)用派生類析構(gòu)函數(shù),再調(diào)用基類析構(gòu)函數(shù)。但是,如果用new運算符建立了一個派生類臨時對象,但用一個基類指針指向它,當程序用帶指針參數(shù)的delete撤消對象時,會發(fā)生讓人不能接受的情況:系統(tǒng)只析構(gòu)基類對象,而不析構(gòu)派生類對象(即只會調(diào)用基類的析構(gòu)函數(shù),而不會調(diào)用派生類的析構(gòu)函數(shù),這就使得派生類無法執(zhí)行某些清理工作):classpoint{public:point(){}~point(){cout<<“析構(gòu)基類對象”<<endl;}};classcircle:publicpoint{public:circle(){}~circle(){cout<<“析構(gòu)派生類對象”<<endl;}private:intradius;};intmain(){point*p=newcircle;

//指針為指向基類對象指針,

//但實際指向臨時派生類對象deletep;return0;}

解決的辦法:可以將基類的析構(gòu)函數(shù)聲明為虛析構(gòu)函數(shù)。如:

virtual~point(){cout<<“析構(gòu)基類對象”<<endl;}

程序其它部分不動,就行了。 當基類的析構(gòu)函數(shù)被定義成virtual,無論指針指向同一類族中的哪一個對象,當撤消對象時,系統(tǒng)會采用動態(tài)關(guān)聯(lián),調(diào)用相應的析構(gòu)函數(shù),清理該對象,然后再析構(gòu)基類對象。其他需要注意的問題:要實現(xiàn)運行時的多態(tài),需要以下條件:(1)必須通過指向基類對象的指針,訪問和基類成員函數(shù)同名的派生類成員函數(shù)?;蛘哂门缮悓ο蟪跏蓟幕悓ο蟮囊?,訪問和基類成員函數(shù)同名的派生類成員函數(shù)。(2)派生類的繼承方式必須是公有繼承。(3)基類中的同名成員函數(shù)必須定義為虛函數(shù)。其他需要注意的問題:使用虛函數(shù)時要遵循以下規(guī)則:(1)必須首先在基類中聲明虛函數(shù)。在多級繼承的情況下,也可以不在最高層的基類中聲明虛函數(shù)。例如在第二層定義的虛函數(shù),可以和第三層的虛函數(shù)形成動態(tài)聯(lián)編。但是,一般都是在最高層的基類中首先聲明虛函數(shù)。(2)基類和派生類的同名函數(shù),函數(shù)名、返回值、參數(shù)表必須全部相同,才能作為虛函數(shù)來使用。否則,即使函數(shù)用virtual來說明,也不具有虛函數(shù)的行為。(3)靜態(tài)成員函數(shù)不可以聲明為虛函數(shù)。構(gòu)造函數(shù)也不可以聲明為虛函數(shù)。(4)析構(gòu)函數(shù)可以聲明為虛函數(shù),即可以定義虛析構(gòu)函數(shù)。(5)如果在多層繼承中,最高層和第三層有兩個原型相同的函數(shù),并在最高層中聲明為虛函數(shù),則第三層的這個函數(shù)也是虛函數(shù)。這種關(guān)系不會因為第二層沒有定義這個函數(shù)而受到影響。例9-7。9.4純虛函數(shù)與抽象類 前面已經(jīng)提到,有時在基類中將某一成員函數(shù)定為虛函數(shù)并不是基類本身的需要,而是派生類的需要。在基類中預留一個函數(shù)名,具體功能留給派生類根據(jù)需要去定義。 在上一節(jié)中基類point中沒有定義面積area函數(shù),是因為“點”沒有面積的概念。但是,其直接派生類circle和間接派生類cylinder卻都需要area函數(shù),而且這兩個area函數(shù)的功能還不相同,一個是求圓面積,一個是求圓柱體表面積。 也許會想到,在基類point中加一個area函數(shù),并聲明為虛函數(shù):

virtualfloatarea()const{return0;}

其返回值為0表示“點”沒有面積。其實,在基類中并不使用這個函數(shù),其返回值也沒有意義。9.4純虛函數(shù)與抽象類 為簡化起見,可以不寫出這種無意義的函數(shù)體,只給出函數(shù)的原型,并在后面加上“=0”,如:

virtualfloatarea()const=0;//純虛函數(shù) 這就將area聲明為一個純虛函數(shù)(purevirtualfunction)純虛函數(shù)的聲明形式

virtual函數(shù)類型函數(shù)名(參數(shù)表)=0; 說明純虛函數(shù)沒有函數(shù)體;最后的“=0”不表示函數(shù)值返回0,它只是形式上的作用,告訴編譯系統(tǒng):這是純虛函數(shù),這是一個聲明語句,以分號結(jié)尾。如果基類中聲明了純虛函數(shù),但派生類中沒有定義該函數(shù),則該函數(shù)在派生類中仍為純虛函數(shù)。9.4純虛函數(shù)與抽象類抽象類什么叫抽象類?一般聲明了一個類,用來定義若干對象。但有些類并不是用來生成對象,而是作為基類去建立派生類。這種不用來定義對象,而只作為一種基本類型用做繼承的類,就叫抽象類(abstractclass)。由于抽象類常作為基類,我們也稱為抽象基類(abstractbaseclass)。 比如,凡是包含純虛函數(shù)的類都叫抽象類。因為純虛函數(shù)不能被調(diào)用,包含純虛函數(shù)的類無法建立對象。抽象類的作用:是作為一個類族的共同基類。即,為一個類族提供一個公共接口。 一個類層次結(jié)構(gòu)中可以不包含任何抽象類,每一層次的類都可以建立對象。但是,許多系統(tǒng)的頂層是一個抽象類,甚至頂部有好幾層都是抽象類。9.4純虛函數(shù)與抽象類 如果由抽象類所派生出的新類中對基類的所有純虛函數(shù)都進行了定義,這個派生類就不是抽象類,可以被調(diào)用,這個派生類就成為可以用來定義對象的具體類(concreteclass)。 如果由抽象類所派生出的新類中對基類的純虛函數(shù)沒有都進行定義,這個派生類就仍然是抽象類。 雖然抽象類不能定義對象,但可以定義指向抽象類的數(shù)據(jù)的指針。當派生類成為具體類之后,就可以用這種指針指向派生類對象,然后通過該指針調(diào)用虛函數(shù),實現(xiàn)多態(tài)性操作。

下面請看一個示例,對之前介紹過的以point為基類的點-圓-圓柱體類的層次結(jié)構(gòu)進行改寫,類的頂層是抽象基類shape,點、圓、圓柱體都是shape的直接或間接派生類。#include<iostream.h>classshape{public:virtualfloatarea()const{return0.0;}//虛函數(shù)

virtualfloatvolume()const{return0.0;}//虛函數(shù)

virtualvoidshapeName()const=0;

//純虛函數(shù)};//抽象類不能也不必定義對象classpoint:publicshape{public:point(float=0,float=0);voidsetPoint(float,float);floatgetX(){returnx;}floatgetY(){returny;}virtualvoidshapeName()const{cout<<“point:”;}//對虛函數(shù)再定義

friendostream&operator<<(ostream&,constpoint&);protected:floatx,y;};point::point(floata,floatb){x=a,y=b;}voidpoint::setPoint(floata,floatb){x=a;y=b;}ostream&operator<<(ostream&output,constpoint&p){output<<“[“<<p.x<<“,”<<p.y<<“]”;returnoutput;}classcircle:publicpoint{public:circle(floatx=0,floaty=0,floatr=0);floatgetRadius()const;virtualfloatarea()const;//因為基類此函數(shù)定義為虛函數(shù),所以在這里,不管有沒有virtual,只要函數(shù)名、參數(shù)表相同,都是虛函數(shù)virtualvoidshapeName()const{cout<<“circle:”;}//對虛函數(shù)再定義

friendostream&operator<<(ostream&,constcircle&);protected:floatradius;};//定義circle類成員函數(shù)circle::circle(floatx,floaty,floatr):point(x,y),radius(r){}floatcircle::getRadius()const{returnradius;}floatcircle::area()const{return3.14159*radius*radius;}//重新定義了area函數(shù)ostream&operator<<(ostream&output,constcircle&c){output<<“[“<<c.x<<“,”<<c.y<<“],r=”<<c.radius;returnoutput;}//volume對于point、circle類沒有意義,只是簡單繼承過來,而沒有重新定義classcylinder:publiccircle{public:cylinder(floatx=0,floaty=0,floatr=0,floath=0);voidsetHeight(float);virtualfloatarea()const;virtualfloatvolume()const;virtualvoidshapeName()const{cout<<“cylinder:”;}//對虛函數(shù)再定義

friendostream&operator<<(ostream&,constcylinder&);protected:floatheight;};//定義cylinder類成員函數(shù)cylinder::cylinder(floata,floatb,floatr,floath):circle(a,b,r),height(h){}voidcylinder::setHeight(floath){height=h;}floatcylinder::area()const{return2*circle::area()+2*3.14159*radius*height;}//重新定義了area函數(shù)floatcylinder::volume()const{returncircle::area()*height;}//重新定義了volume函數(shù)ostream&operator<<(ostream&output,constcylinder&cy){output<<“[“<<cy.x<<“,”<<cy.y<<“],r=”<<cy.radius<<“,h=“<<cy.height;returnoutput;}voidmain(){pointp1(3.2,4.5);circlec1(2.4,1.2,5.6);cylindercy1(3.5,6.4,5.2,10.5);p1.shapeName();//靜態(tài)關(guān)聯(lián)cout<<p1<<endl;c1.shapeName();//靜態(tài)關(guān)聯(lián)cout<<c1<<endl;cy1.shapeName();//靜態(tài)關(guān)聯(lián)cout<<cy1<<endl;

shape*pt;//定義基類指針

pt=&p1;//指針指向point對象p1pt->shapeName();//動態(tài)關(guān)聯(lián)

cout<<“x=“<<p1.getX()<<“,y=“<<p1.getY()<<“\narea=“<<pt->area()<<“\nvolume=“<<pt->volume()<<“\n\n”;pt=&c1;//指針指向circle類對象c1pt->shapeName();//動態(tài)關(guān)聯(lián)cout<<“x=“<<c1.getX()<<“,y=“<<c1.getY()<<“\narea=“<<pt->area()<<“\nvolume=“<<pt->volume()<<“\n\n”;pt=

&cy1;//指針指向cylinder類對象cy1pt->shapeName();//動態(tài)關(guān)聯(lián)cout<<“x=“<<cy1.getX()<<“,y=“<<cy1.getY()<<“\narea=“<<pt->area()<<“\nvolume=“<<pt->volume()<<“\n\n”;}//區(qū)別靜態(tài)關(guān)聯(lián)和動態(tài)關(guān)聯(lián)的方法://.如果是通過對象名(非引用)調(diào)用虛函數(shù),如://p1.shapeName(),就是靜態(tài)關(guān)聯(lián);//.如果是通過基類指針、或引用調(diào)用虛函數(shù),如://pt->shapeName(),就是動態(tài)關(guān)聯(lián)。 多態(tài)性把操作細節(jié)留給類的設計者(專業(yè)人員)去完成,而讓編程人員(類的使用者)只需做一些宏觀性的工作,告訴系統(tǒng)做什么,不必考慮怎么做,簡化了應用程序的編碼工作。 因此有人說,多態(tài)性是開啟繼承功能的鑰匙。為什么需要模板?

C++引入模板主要的目的,是為了使代碼具有好的可重用特性。在C++中,妨礙代碼重用的原因之一在于函數(shù)以及類都和類型有很強的關(guān)聯(lián)性,如果一個函數(shù)在定義時只能處理整型類型的輸入?yún)?shù),則該函數(shù)就不能處理字符型或其它程序員自定義的類型。類也具有相同的問題,類中的數(shù)據(jù)成員往往也具有確定的類型。模板的引入正是為了解決在這種情形下的代碼重用問題。9.5模板模板把函數(shù)或類要處理的數(shù)據(jù)類型參數(shù)化,表現(xiàn)為參數(shù)的多態(tài)性。使得程序(算法)可以從邏輯功能上抽象把被處理的對象(數(shù)據(jù))類型作為參數(shù)傳遞C++提供兩種模板機制: 函數(shù)模板類模板模板(Template)函數(shù)模板(FunctionTemplate)類模板

(ClassTemplate)模板函數(shù)(TemplateFunction)模板類(TemplateClass)對象(Object)9.5.1函數(shù)模板先看一個例子。

voidswap(int&a,int&b){

inttemp=a;

a=b;

b=temp;}9.5.1函數(shù)模板 重載提供了函數(shù)的另外一種定義,但不是代碼重用。voidswap(double&a,double&b){

doubletemp=a;a=b;b=temp;}voidswap(student&a,student&b){

studenttemp=a;a=b;b=temp;}9.5.1函數(shù)模板

可以通過定義函數(shù)模板的方式解決因類型不同,參數(shù)個數(shù)相同而導致的代碼不能重用的問題。 在函數(shù)模板中,類型本身可被定義成模板參數(shù)。 通過函數(shù)模板定義的函數(shù)不是一個函數(shù),而是一組函數(shù)。 定義函數(shù)模板使用保留字template,定義格式如下:template<模板參數(shù)表>返回類型函數(shù)名(函數(shù)形式參數(shù)表){

...

}9.5.1函數(shù)模板 模板參數(shù)表中的參數(shù)可以有多個,多個參數(shù)間用逗號間隔。模板參數(shù)通常代表一個類型(模板類型參數(shù),注意和函數(shù)參數(shù)不同),模板類型參數(shù)形式如下:class類型參數(shù)名(或typename類型參數(shù)名)

在模板函數(shù)的函數(shù)定義中,可以使用模板參數(shù)表中的類型參數(shù),就象使用一個基本數(shù)據(jù)類型或用戶已定義好的類型一樣。template<typenameT>voidswap(T&a,T&b){Ttemp=a;a=b;b=temp;}9.5.1函數(shù)模板調(diào)用模板函數(shù)示例:intix=6,iy=7;swap(ix,iy);函數(shù)模板的例化(instaniation)voidswap(int&a,int&b){

inttemp=a;a=b;b=temp;}T=intvoidswap(double&a,double&b){doubletemp=a;a=b;b=temp;}T=double9.5.1函數(shù)模板在調(diào)用函數(shù)模板時,C++編譯器通常是根據(jù)函數(shù)實參的類型來例化函數(shù)模板,這個過程由C++編譯器自動進行,稱為模板參數(shù)推導(templateargumentdeduction)。程序員在調(diào)用模板函數(shù)時也可顯式指定用以替換模板類型參數(shù)的模板實參,模板函數(shù)的顯式調(diào)用形式如下:模板函數(shù)名<模板實參表>(函數(shù)實參表);如:floatfx=6.5,fy=7.5;

swap<float>(fx,fy);指定的模板實參應和函數(shù)實參類型匹配。如:intix=6,iy=7;swap<float>(ix,iy);//錯誤函數(shù)模板的用法使用函數(shù)模板時,應保證函數(shù)的參數(shù)與模板函數(shù)的參數(shù)匹配,編譯器不會給模板函數(shù)的參數(shù)提供任何形式的轉(zhuǎn)換。事實上,函數(shù)模板表示了一組名字相同的函數(shù),這些函數(shù)之間,以及這些函數(shù)與其它同名函數(shù)之間,是重載函數(shù)的關(guān)系。在引入模板后,重載函數(shù)的參數(shù)匹配規(guī)則如下:若函數(shù)的參數(shù)正好和調(diào)用函數(shù)的參數(shù)匹配,則調(diào)用此函數(shù);否則,從函數(shù)模板生成函數(shù),如果函數(shù)實參正好和生成函數(shù)的參數(shù)匹配,則調(diào)用該模板函數(shù);否則,對相應函數(shù)實參做類型轉(zhuǎn)換之后再匹配,如果匹配成功,則調(diào)用相應函數(shù)。否則,編譯系統(tǒng)會產(chǎn)生錯誤提示信息。9.5.1函數(shù)模板9.5.1函數(shù)模板測試重載函數(shù)的匹配規(guī)則示例。#include<iostream>usingnamespacestd;template<classT>Tmax(Ta,intb);doublemax(doublea,intb)Voidmain(){inti1,i2,i3;doubled1,d2,d3;i1=3,i2=9;d1=6.7,i3=10;d2=8.4,d3=7.8;cout<<max(i1,d3)<<endl;cout<<max(d1,i3)<<endl;cout<<max(d2,d3)<<endl;}doublemax(doublea,intb){cout<<“doublemax(double,int):”;returna>b?a:b;}template<classT>Tmax(Ta,intb){returna>b?a:b;}9.5.1函數(shù)模板顯式指明模板實參示例:程序員可以僅提供部分模板實參,其余的模板實參仍由編譯器自動推導。且省略掉的實參必須是模板參數(shù)表尾部的參數(shù)對應的實參。template<typenameT1,typenameT2,typenameT3>

T1sum(T2a,T3b){returna+b;}

...

intx=6;

floaty=9.9,sigma;

sigma=sum(x,y);//錯誤

sigma=sum<float,int,float>(x,y);//正確

sigma=sum<float>(x,y);//正確

sigma=sum<,int>(x,y);//錯誤9.5.2類模板和函數(shù)一樣,類同樣具有類型依賴性。

類模板實際上是函數(shù)模板的推廣。類模板用于實現(xiàn)類所需數(shù)據(jù)的類型參數(shù)化。9.5.2類模板為了解決類依賴類型的問題,可以使用類模板,類模板的定義和函數(shù)模板的定義類似,使用保留字template,格式如下:template<模板參數(shù)表>class類名{

…;};模板參數(shù)表中的參數(shù)可以有多個,多個參數(shù)間用逗號間隔。模板參數(shù)通常代表一個類型,模板類型參數(shù)形式如下:class類型參數(shù)名(或typename類型參數(shù)名)

9.5.2類模板當定義一個模板類的對象時,程序員需要提供模板實參,C++編譯器根據(jù)程序員提供的模板實參生成一個個具體的適合不同類型的類定義,形式如下:

類名<模板實參>對象名

...

Stash<int>intStash;

Stash<float>floatStash;classStash{intquantity;

intnext;

int*storage;

public:

Stash();

Stash(intinitQuantity);

~Stash();

intadd(int&element);

int&fetch(intindex)const;

intcount()const;

};9.5.2類模板Template<templateT>

classStash{intquantity;

intnext;

T*storage;

public:

Stash();

Stash(intinitQuantity);

~Stash();

intadd(T&element);

T&fetch(intindex)const;

intcount()const;

};9.5.2類模板C++編譯器根據(jù)模板實參生成類的過程被稱為類模板例化。classStash{

intquantity;

intnext;

int*storage;

public:

Stash();

Stash(intinitQuantity);

~Stash();

intadd(int&element);

int&fetch(intindex)const;

intcount()const;

};Stash<int>T=intclassStash{

intquantity;

intnext;

student*storage;

public:

Stash();

Stash(intinitQuantity);

~Stash();

intadd(student&element);

student&fetch(intindex)const;

intcount()const;

};Stash<student>T=student9.5.2類模板模板參數(shù)可以分成兩類:

類型參數(shù)

非類型參數(shù)非類型參數(shù)形式如下:

類型參數(shù)名

...

template<classT,intinc>

classStash{...};如果模板參數(shù)表中含有非類型參數(shù),定義對象時,程序員需要提供作為實參的常量值。這相當于在程序中定義了一個常量,如:Stash<int,50>intStash;

//等同于constintinc=509.5.2類模板對于非類型參數(shù),可以有默認值。如:

template<classT,intinc=50>

classStash{...};同函數(shù)默認值一樣,默認值的指定必須按照從右向左的順序進行。

template<classT,intx=50,inty=100>//正確

template<classT,intx,inty=100>//正確

template<classT,intx=50,inty>//錯誤如果指定了默認值,定義對象時,如果程序員沒有提供模板實參,則表示使用默認值。如:stash<int>intStash;//等同于stash<int,50>intS

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論