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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

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

friendostream&operator<<(ostream&,constpoint&);protected:floatx,y;};//下面定義point類的成員函數point::point(floata,floatb)//point的構造函數{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;}編寫好左邊程序后,可調試它的正確性。我們編寫一個臨時的main函數:Voidmain(){pointp(1.1,2.2);cout<<p<<endl;p.setPoint(3.3,4.4);cout<<p<<endl;}

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

:point(a,b),radius(r){}//point的構造函數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;}編寫好左邊程序后,可調試它的正確性。我們編寫一個臨時的main函數:Voidmain(){circlec(1.1,2.2,1.0);c.setRadius(2.0);c.setPoint(2.0,2.0);cout<<c<<endl;}

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

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

:circle(a,b,r),height(h){}//point的構造函數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;}最后寫出主函數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虛函數與運行時多態(tài)

上節(jié)介紹的程序中,circle類和cylinder類都定義了area函數,但功能不一樣。前者是求圓面積,后者是求圓柱體表面積。由于處在不同的類中,同名是合法的。 如果在圓柱體對象cy1中要調用求圓面積的函數,則應該寫成:cy1.circle::area(),而不能寫成cy1.area()。用這種方法來區(qū)分兩種同名函數。使用起來不方便。 人們要問,能否用一個調用形式,既能調用派生類的函數,又能調用基類同名函數?C++中的虛函數就是用來解決這一問題。虛函數的作用:虛函數的作用是允許在派生類中重新定義與基類同名的函數,并且可以通過基類指針或引用來訪問基類和派生類中的同名函數。請看示例程序:示例#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,僅輸出了派生類的基類數據成員,因為它調用的是基類成員函數display!}假如想輸出派生類的全部數據,當然可以采用下面兩種方法之一:通過派生類對象名g1,調用派生類對象的成員函數:g1.display();定義一個指向派生類的指針ptr,并指向g1,然后用ptr->display()。虛函數 我們可以用虛函數可以順利解決這一問題。方法是:在基類student中聲明display函數時,在最左邊加上一個關鍵字virtual:

virtualvoiddisplay();

就可以student類的display函數聲明為虛函數,程序其它部分不變編譯運行后,可見,使用pt->display(),的確將graduate類對象g1的全部數據顯示了出來,說明它調用的是g1的成員函數display。在派生類中重新定義該函數,要求函數名、函數類型、參數表完全相同。只在類里的成員函數聲明時,加上關鍵字virtual,在類外定義定義虛函數時,不必加virtual關鍵字。定義一個指向基類對象的指針,并使她指向同一類族中的某一對象;通過該指針變量調用此虛函數,此時調用的就是指針變量指向的對象的同名函數。虛函數示例 通過使用虛函數和指針,就能方便地調用同一類族中不同類對象的同名函數,只要先用基類指針指向該對象即可。#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,調用g1的顯示函數display,輸出g1全部數據成員}虛函數示例

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

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

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

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

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

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

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

解決的辦法:可以將基類的析構函數聲明為虛析構函數。如:

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

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

virtualfloatarea()const{return0;}

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

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

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

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

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

virtualvoidshapeName()const=0;

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

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;//因為基類此函數定義為虛函數,所以在這里,不管有沒有virtual,只要函數名、參數表相同,都是虛函數virtualvoidshapeName()const{cout<<“circle:”;}//對虛函數再定義

friendostream&operator<<(ostream&,constcircle&);protected:floatradius;};//定義circle類成員函數circle::circle(floatx,floaty,floatr):point(x,y),radius(r){}floatcircle::getRadius()const{returnradius;}floatcircle::area()const{return3.14159*radius*radius;}//重新定義了area函數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:”;}//對虛函數再定義

friendostream&operator<<(ostream&,constcylinder&);protected:floatheight;};//定義cylinder類成員函數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函數floatcylinder::volume()const{returncircle::area()*height;}//重新定義了volume函數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)關聯cout<<p1<<endl;c1.shapeName();//靜態(tài)關聯cout<<c1<<endl;cy1.shapeName();//靜態(tài)關聯cout<<cy1<<endl;

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

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

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

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

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

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

voidswap(int&a,int&b){

inttemp=a;

a=b;

b=temp;}9.5.1函數模板 重載提供了函數的另外一種定義,但不是代碼重用。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函數模板

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

...

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

在模板函數的函數定義中,可以使用模板參數表中的類型參數,就象使用一個基本數據類型或用戶已定義好的類型一樣。template<typenameT>voidswap(T&a,T&b){Ttemp=a;a=b;b=temp;}9.5.1函數模板調用模板函數示例:intix=6,iy=7;swap(ix,iy);函數模板的例化(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函數模板在調用函數模板時,C++編譯器通常是根據函數實參的類型來例化函數模板,這個過程由C++編譯器自動進行,稱為模板參數推導(templateargumentdeduction)。程序員在調用模板函數時也可顯式指定用以替換模板類型參數的模板實參,模板函數的顯式調用形式如下:模板函數名<模板實參表>(函數實參表);如:floatfx=6.5,fy=7.5;

swap<float>(fx,fy);指定的模板實參應和函數實參類型匹配。如:intix=6,iy=7;swap<float>(ix,iy);//錯誤函數模板的用法使用函數模板時,應保證函數的參數與模板函數的參數匹配,編譯器不會給模板函數的參數提供任何形式的轉換。事實上,函數模板表示了一組名字相同的函數,這些函數之間,以及這些函數與其它同名函數之間,是重載函數的關系。在引入模板后,重載函數的參數匹配規(guī)則如下:若函數的參數正好和調用函數的參數匹配,則調用此函數;否則,從函數模板生成函數,如果函數實參正好和生成函數的參數匹配,則調用該模板函數;否則,對相應函數實參做類型轉換之后再匹配,如果匹配成功,則調用相應函數。否則,編譯系統(tǒng)會產生錯誤提示信息。9.5.1函數模板9.5.1函數模板測試重載函數的匹配規(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函數模板顯式指明模板實參示例:程序員可以僅提供部分模板實參,其余的模板實參仍由編譯器自動推導。且省略掉的實參必須是模板參數表尾部的參數對應的實參。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類模板和函數一樣,類同樣具有類型依賴性。

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

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

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

類名<模板實參>對象名

...

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++編譯器根據模板實參生成類的過程被稱為類模板例化。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類模板模板參數可以分成兩類:

類型參數

非類型參數非類型參數形式如下:

類型參數名

...

template<classT,intinc>

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

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

template<classT,intinc=50>

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

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. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論