![第六章C++多態(tài)性與虛函數(shù)_第1頁](http://file4.renrendoc.com/view/79ab94da9f2e93b271eaee09675ea23c/79ab94da9f2e93b271eaee09675ea23c1.gif)
![第六章C++多態(tài)性與虛函數(shù)_第2頁](http://file4.renrendoc.com/view/79ab94da9f2e93b271eaee09675ea23c/79ab94da9f2e93b271eaee09675ea23c2.gif)
![第六章C++多態(tài)性與虛函數(shù)_第3頁](http://file4.renrendoc.com/view/79ab94da9f2e93b271eaee09675ea23c/79ab94da9f2e93b271eaee09675ea23c3.gif)
![第六章C++多態(tài)性與虛函數(shù)_第4頁](http://file4.renrendoc.com/view/79ab94da9f2e93b271eaee09675ea23c/79ab94da9f2e93b271eaee09675ea23c4.gif)
![第六章C++多態(tài)性與虛函數(shù)_第5頁](http://file4.renrendoc.com/view/79ab94da9f2e93b271eaee09675ea23c/79ab94da9f2e93b271eaee09675ea23c5.gif)
版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
第6章多態(tài)性與虛函數(shù)6.1多態(tài)性的概念6.2一個典型的例子6.3虛函數(shù)6.4純虛函數(shù)與抽象類
多態(tài)性(polymorphism)是面向對象程序設計的重要特征。利用多態(tài)性可以設計和實現(xiàn)一個易于擴展的系統(tǒng)。C++程序設計中,多態(tài)性是指具有不同功能的函數(shù),可用同一個函數(shù)名,從而實現(xiàn)用一個函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。面向對象方法中,多態(tài)性描述是:向不同的對象發(fā)送同一個消息,不同的對象在接收時會產(chǎn)生不同的行為(即方法)。即,每個對象可用自己的方式去響應相同的消息。從系統(tǒng)實現(xiàn)的角度,多態(tài)性分為兩類:靜態(tài)多態(tài)性和動態(tài)多態(tài)性。靜態(tài)多態(tài)性,在程序編譯時系統(tǒng)就能決定調(diào)用的是哪個函數(shù),因此靜態(tài)多態(tài)性又稱編譯時的多態(tài)性。靜態(tài)多態(tài)性是通過函數(shù)重載實現(xiàn)(運算符重載實質上也是函數(shù)重載)。6.1多態(tài)性的概念動態(tài)多態(tài)性在程序運行過程中,才動態(tài)確定操作所針對的對象,又稱運行時的多態(tài)性。動態(tài)多態(tài)性通過虛函數(shù)(virtualfunction)實現(xiàn)。
問題:當一個基類被繼承為不同派生類時,各派生類可以使用與基類成員相同的成員名,在運行時如用同一個成員名,調(diào)用類對象的成員,會調(diào)用哪個對象的成員?即:通過繼承而產(chǎn)生了相關的不同派生類,與基類成員同名的成員,在不同派生類中有不同的含義。也可以說,多態(tài)性是“一個接口,多種方法”。6.2一個典型的例子例6.1
先建立一個Point(點)類,包含數(shù)據(jù)成員x,y(坐標點)。以它為基類,派生出一個Circle(圓)類,增加數(shù)據(jù)成員r(半徑)。再以Circle類為直接基類,派生出一個Cylinder(圓柱體)類,再增加數(shù)據(jù)成員h(高)。要求:編寫程序,重載運算符“<<”和“>>”,使之能用于輸出以上類對象。對一個比較大的程序,應當分若干步驟進行。先聲明基類,再聲明派生類,逐級進行,分步調(diào)試。(1)聲明基類Point類#include<iostream>classPoint{public:Point(floatx=0,floaty=0);//有默認參數(shù)的構造函數(shù)voidsetPoint(float,float);//設置坐標值
floatgetX()const{returnx;}//讀x坐標
floatgetY()const{returny;}//讀y坐標
friendostream&operator<<(ostream&,constPoint&);//重載“<<”protected://受保護成員
floatx,y;};Point::Point(floata,floatb)//對x,y初始化{x=a;y=b;}voidPoint::setPoint(floata,floatb)//為x,y賦新值{x=a;y=b;}//重載運算符“<<”,使之能輸出點的坐標ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″<<endl;returnoutput;}現(xiàn)在對上面寫的基類聲明進行調(diào)試,檢查它是否有錯,為此要寫出main函數(shù)。實際上它是一個測試程序。intmain(){Pointp(3.5,6.4);//建立Point類對象p
cout<<″x=″<<p.getX()<<″,y=″<<p.getY()<<endl;//輸出p的坐標值
p.setPoint(8.5,6.8);//重新設置p的坐標值
cout<<″p(new):″<<p<<endl;//用重載運算符“<<”輸出p點坐標}程序編譯通過,運行結果:x=3.5,y=6.4p(new):[8.5,6.8]測試程序檢查了基類中各函數(shù)的功能,以及運算符重載的作用。(2)聲明派生類CircleclassCircle:publicPoint//circle是Point類的公用派生類{public:Circle(floatx=0,floaty=0,floatr=0);//構造函數(shù)
voidsetRadius(float);//設置半徑值
floatgetRadius()const;//讀取半徑值
floatarea()const;//計算圓面積
friendostream&operator<<(ostream&,constCircle&);//重載“<<”
private:floatradius;};Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}voidCircle::setRadius(floatr){radius=r;}floatCircle::getRadius()const{returnradius;}floatCircle::area()const{return3.14159*radius*radius;}//重載運算符“<<”,使之按規(guī)定的形式輸出圓的信息ostream&operator<<(ostream&output,constCircle&c){output<<″Center=[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius<<″,area=″<<c.area()<<endl;returnoutput;}為了測試以上Circle類的定義,寫出下面的主函數(shù):intmain(){Circlec(3.5,6.4,5.2);//建立Circle類對象c,并給定圓心坐標和半徑
cout<<″originalcircle:\\nx=″<<c.getX()<<″,y=″<<c.getY()<<″,r=″<<c.getRadius()<<″,area=″<<c.area()<<endl;c.setRadius(7.5);//設置半徑值
c.setPoint(5,5);//設置圓心坐標值x,y
cout<<″newcircle:\\n″<<c;//用重載“<<”輸出圓對象的信息
Point&pRef=c;//pRef是Point類的引用變量,被c初始化
cout<<″pRef:″<<pRef;//輸出pRef的信息
return0;}(3)聲明Circle的派生類CylinderclassCylinder:publicCircle//Cylinder是Circle的公用派生類{public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);//構造函數(shù)
voidsetHeight(float);//設置圓柱高
floatgetHeight()const;//讀取圓柱高
floatarea()const;//計算圓表面積
floatvolume()const;//計算圓柱體積
friendostream&operator<<(ostream&,constCylinder&);//重載“<<”
protected:floatheight;//圓柱高};Cylinder::Cylinder(floata,floatb,floatr,floath):Circle(a,b,r),height(h){}voidCylinder::setHeight(floath){height=h;}floatCylinder::getHeight()const{returnheight;}floatCylinder::area()const//表面積{return2*Circle::area()+2*3.14159*radius*height;}floatCylinder::volume()const//體積{returnCircle::area()*height;}ostream&operator<<(ostream&output,constCylinder&cy){output<<″Center=[″<<cy.x<<″,″<<cy.y<<″],r=″<<cy.radius<<″,h=″<<cy.height<<″\\narea=″<<cy.area()<<″,volume=″<<cy.volume()<<endl;returnoutput;}主函數(shù):intmain(){Cylindercy1(3.5,6.4,5.2,10);//定義Cylinder類對象cy1
cout<<″x=″<<cy1.getX()<<″,y=″<<cy1.getY()<<″,r=″<<cy1.getRadius()<<″,h=″<<cy1.getHeight()<<″area=″<<cy1.area()<<″,volume=″<<cy1.volume()<<endl;//用系統(tǒng)定義的運算符“<<”輸出cy1的數(shù)據(jù)
cy1.setHeight(15);//設置圓柱高
cy1.setRadius(7.5);//設置圓半徑
cy1.setPoint(5,5);//設置圓心坐標值x,y
cout<<″newcylinder:″<<cy1;//用重載<<輸出cy1的數(shù)據(jù)
Point&pRef=cy1;//pRef是Point類對象的引用變量
cout<<″pRefasaPoint:″<<pRef;//pRef作為一個“點”輸出
Circle&cRef=cy1;//cRef是Circle類對象的引用變量
cout<<″cRefasaCircle:″<<cRef;//cRef作為一個“圓”輸出
return0;}運行結果:originalcylinder:(輸出cy1的初始值)x=3.5,y=6.4,r=5.2,h=10(圓心坐標x,y。半徑r,高h)area=496.623,volume=849.486(圓柱表面積area和體積volume)newcylinder:(輸出cy1的新值)Center=[5,5],r=7.5,h=15(以[5,5]形式輸出圓心坐標)area=1060.29,volume=2650.72(圓柱表面積area和體積volume)pRefasaPoint:[5,5](pRef作為一個“點”輸出)cRefasaCircle:Center=[5,5],r=7.5,area=176.714(cRef作為“圓”輸出)本例中存在靜態(tài)多態(tài)性,這是運算符重載引起的。可見,編譯時編譯系統(tǒng)即可以判定應調(diào)用哪個重載運算符函數(shù)。6.3.1虛函數(shù)的作用在類繼承層次結構中,在不同層次,可以出現(xiàn)名字相同、參數(shù)個數(shù)和類型都相同而功能不同的函數(shù)。編譯系統(tǒng)按照同名覆蓋原則,決定所調(diào)用的對象。在例6.1中,用cy1.area()調(diào)用的是派生類Cylinder中的成員函數(shù)area。如想調(diào)用cy1中的、直接基類Circle的area函數(shù),應表示為:cy1.Circle::area()。用這種方法來區(qū)分兩個同名的函數(shù)。
設想:能否用同一個調(diào)用形式,既能調(diào)用派生類,又能調(diào)用基類的同名函數(shù)?在程序中,不通過不同的對象名,去調(diào)用不同派生層次中的同名函數(shù),而是通過指針調(diào)用它們。如:用同一個語句“pt->display();”可以調(diào)用不同派生層次中的display函數(shù),只需在調(diào)用前,給指針變量pt賦以不同的值(使之指向不同的類對象)即可。6.3虛函數(shù)
C++中,用虛函數(shù)來解決此問題。虛函數(shù)作用是:允許在派生類中重新定義與基類同名的函數(shù),并可以通過基類指針或引用,來訪問基類和派生類中的同名函數(shù)。例6.2
基類與派生類中有同名函數(shù)。#include<iostream>#include<string>usingnamespacestd;classStudent{public:
Student(int,string,float);//聲明構造函數(shù)
voiddisplay();//聲明輸出函數(shù)
protected://受保護成員,派生類可以訪問
intnum;stringname;floatscore;};Student::Student(intn,stringnam,floats)//定義構造函數(shù){num=n;name=nam;score=s;}voidStudent::display()//定義輸出函數(shù){cout<<″num:″<<num<<″name:″<<name<<″score:″<<score<<;}classGraduate:publicStudent{public:
Graduate(int,string,float,float);//聲明構造函數(shù)
voiddisplay();//聲明輸出函數(shù)private:floatpay;};voidGraduate::display()//定義輸出函數(shù){cout<<″num:″<<num<<″name:″<<name<<″score:″<<score<<″pay=″<<pay<<endl;}Graduate::Graduate(intn,stringnam,floats,floatp):Student(n,nam,s),pay(p){}
intmain(){Studentstud1(1001,″Li″,87.5);//定義Student類對象stud1Graduategrad1(2001,″Wang″,98.5,563.5);//定義Graduate類對象Student*pt=&stud1;//定義指向基類對象的指針變量ptpt->display();pt=&grad1;pt->display();return0;}運行結果:num:1001(stud1的數(shù)據(jù))name:Liscore:87.5num:2001(grad1中基類部分的數(shù)據(jù))name:wangscore:98.5下面進行修改:在Student類中聲明display函數(shù)時,在最左面加一個關鍵字virtual,即:virtualvoiddisplay();即把Student類的display函數(shù)聲明為虛函數(shù)。程序其他部分都不改動。再編譯和運行程序,運行結果:num:1001(stud1的數(shù)據(jù))name:Liscore:87.5num:2001(grad1中基類部分的數(shù)據(jù))name:wangscore:98.5pay=1200(這一項以前是沒有的)由虛函數(shù)實現(xiàn)的動態(tài)多態(tài)性就是:同一類族中不同類的對象,對同一函數(shù)調(diào)用作出不同的響應。虛函數(shù)使用方法:(1)在基類用virtual聲明成員函數(shù)為虛函數(shù)。這樣就可以在派生類中重新定義此函數(shù),為它賦予新的功能,并能方便地被調(diào)用。在類外定義虛函數(shù)時,不必再加virtual。(2)
在派生類中重新定義此函數(shù),要求函數(shù)名、函數(shù)類型、函數(shù)參數(shù)個數(shù)和類型全部與基類的虛函數(shù)相同,并根據(jù)派生類的需要重新定義函數(shù)體。C++規(guī)定,當一個成員函數(shù)被聲明為虛函數(shù)后,其派生類中的同名函數(shù)都自動成為虛函數(shù)。因此在派生類重新聲明該虛函數(shù)時,可以加virtual,也可以不加,但習慣上一般在每一層聲明該函數(shù)時都加virtual,使程序更加清晰。如果在派生類中沒有對基類的虛函數(shù)重新定義,則派生類簡單地繼承其直接基類的虛函數(shù)。(3)定義一個指向基類對象的指針變量,并使它指向同一類族中需要調(diào)用該函數(shù)的對象。(4)通過該指針變量調(diào)用此虛函數(shù),此時調(diào)用的就是指針變量指向的對象的同名函數(shù)。通過虛函數(shù)與指向基類對象的指針變量的配合使用,就能方便地調(diào)用同一類族中、不同類的同名函數(shù),只要先用基類指針指向即可。如果指針不斷指向同一類族中不同類的對象,就能不斷地調(diào)用這些對象中的同名函數(shù)。說明;有時,在基類中定義的非虛函數(shù),會在派生類中被重新定義(如例6.1中的area函數(shù)),如果用基類指針調(diào)用該成員函數(shù),則系統(tǒng)會調(diào)用對象中基類部分的成員函數(shù);如果用派生類指針調(diào)用該成員函數(shù),則系統(tǒng)會調(diào)用派生類對象中的成員函數(shù),這并不是多態(tài)性行為(使用的是不同類型的指針),沒有用到虛函數(shù)的功能。函數(shù)重載,處理的是同一層次上的同名函數(shù)問題,而虛函數(shù)處理的是不同派生層次上的同名函數(shù)問題,前者是橫向重載,后者可以理解為縱向重載。與重載不同的是:同一類族的虛函數(shù)的首部是相同的,而函數(shù)重載時函數(shù)的首部是不同的(參數(shù)個數(shù)或類型不同)。編譯系統(tǒng)要根據(jù)已有的信息,對同名函數(shù)的調(diào)用作出判斷。對于調(diào)用同一類族中的虛函數(shù),應當在調(diào)用時用一定的方式告訴編譯系統(tǒng),你要調(diào)用的是哪個類對象中的函數(shù)。這樣編譯系統(tǒng)在對程序進行編譯時,即能確定調(diào)用的是哪個類對象中的函數(shù)。確定調(diào)用的具體對象的過程稱為關聯(lián)(binding)。在這里是指把一個函數(shù)名與一個類對象捆綁在一起,建立關聯(lián)。一般地說,關聯(lián)指把一個標識符和一個存儲地址聯(lián)系起來。前面所提到的函數(shù)重載和通過對象名調(diào)用的虛函數(shù),在編譯時即可確定其調(diào)用的虛函數(shù)屬于哪一個類,其過程稱為靜態(tài)關聯(lián)(staticbinding),由于是在運行前進行關聯(lián)的,故又稱為早期關聯(lián)(earlybinding)。函數(shù)重載屬靜態(tài)關聯(lián)。6.3.2靜態(tài)關聯(lián)與動態(tài)關聯(lián)
在上一小節(jié)程序中看到了怎樣使用虛函數(shù),在調(diào)用虛函數(shù)時并沒有指定對象名,那么系統(tǒng)是怎樣確定關聯(lián)的呢?是通過基類指針與虛函數(shù)的結合來實現(xiàn)多態(tài)性的。先定義了一個指向基類的指針變量,并使它指向相應的類對象,然后通過這個基類指針去調(diào)用虛函數(shù)(例如“pt->display()”)。顯然,對這樣的調(diào)用方式,編譯系統(tǒng)在編譯該行時是無法確定調(diào)用哪一個類對象的虛函數(shù)的。因為編譯只作靜態(tài)的語法檢查,光從語句形式是無法確定調(diào)用對象的。在這樣的情況下,編譯系統(tǒng)把它放到運行階段處理,在運行階段確定關聯(lián)關系。在運行階段,基類指針變量先指向了某一個類對象,然后通過此指針變量調(diào)用該對象中的函數(shù)。此時調(diào)用哪一個對象的函數(shù)無疑是確定的。例如,先使pt指向grad1,再執(zhí)行“pt->display()”,當然是調(diào)用grad1中的display函數(shù)。由于是在運行階段把虛函數(shù)和類對象“綁定”在一起的,因此,此過程稱為動態(tài)關聯(lián)(dynamicbinding)。這種多態(tài)性是動態(tài)的多態(tài)性,即運行階段的多態(tài)性。在運行階段,指針可以先后指向不同的類對象,從而調(diào)用同一類族中不同類的虛函數(shù)。由于動態(tài)關聯(lián)是在編譯以后的運行階段進行的,因此也稱為滯后關聯(lián)(latebinding)。使用虛函數(shù)時,有兩點要注意:(1)只能用virtual聲明類的成員函數(shù),使它成為虛函數(shù),而不能將類外的普通函數(shù)聲明為虛函數(shù)。因為虛函數(shù)的作用是允許在派生類中對基類的虛函數(shù)重新定義。顯然,它只能用于類的繼承層次結構中。(2)一個成員函數(shù)被聲明為虛函數(shù)后,在同一類族中的類就不能再定義一個非virtual的但與該虛函數(shù)具有相同的參數(shù)(包括個數(shù)和類型)和函數(shù)返回值類型的同名函數(shù)。根據(jù)什么考慮是否把一個成員函數(shù)聲明為虛函數(shù)呢?要考慮以下幾點:(1)首先看成員函數(shù)所在的類是否會作為基類。然后看成員函數(shù)在類的繼承后有無可能被更改功能,如果希望更改其功能的,一般應該將它聲明為虛函數(shù)。6.3.3在什么情況下應當聲明虛函數(shù)(2)如果成員函數(shù)在類被繼承后功能不需修改,或派生類用不到該函數(shù),則不要把它聲明為虛函數(shù)。不要僅僅考慮到要作為基類而把類中的所有成員函數(shù)都聲明為虛函數(shù)。(3)應考慮對成員函數(shù)的調(diào)用是通過對象名還是通過基類指針或引用去訪問,如果是通過基類指針或引用去訪問的,則應當聲明為虛函數(shù)。(4)有時,在定義虛函數(shù)時,并不定義其函數(shù)體,即函數(shù)體是空的。它的作用只是定義了一個虛函數(shù)名,具體功能留給派生類去添加。在12.4節(jié)中將詳細討論此問題。說明:使用虛函數(shù),系統(tǒng)要有一定的空間開銷。當一個類帶有虛函數(shù)時,編譯系統(tǒng)會為該類構造一個虛函數(shù)表(virtualfunctiontable,簡稱vtable),它是一個指針數(shù)組,存放每個虛函數(shù)的入口地址。系統(tǒng)在進行動態(tài)關聯(lián)時的時間開銷是很少的,因此,多態(tài)性是高效的。析構函數(shù)的作用是在對象撤銷之前做必要的“清理現(xiàn)場”的工作。當派生類的對象從內(nèi)存中撤銷時一般先調(diào)用派生類的析構函數(shù),然后再調(diào)用基類的析構函數(shù)。但是,如果用new運算符建立了臨時對象,若基類中有析構函數(shù),并且定義了一個指向該基類的指針變量。在程序用帶指針參數(shù)的delete運算符撤銷對象時,會發(fā)生一個情況:系統(tǒng)會只執(zhí)行基類的析構函數(shù),而不執(zhí)行派生類的析構函數(shù)。6.3.4虛析構函數(shù)例6.3
基類中有非虛析構函數(shù)時的執(zhí)行情況。#include<iostream>usingnamespacestd;classPoint//定義基類Point類{public:Point(){}//Point類構造函數(shù)~Point(){cout<<″executingPointdestructor″<<endl;}//Point類析構函數(shù)};classCircle:publicPoint//定義派生類Circle類{public:Circle(){}//Circle類構造函數(shù)~Circle(){cout<<″executingCircledestructor″<<endl;}//Circle類析構函數(shù)
private:
intradius;};intmain(){Point*p=newCircle;//用new開辟動態(tài)存儲空間deletep;//用delete釋放動態(tài)存儲空間return0;}這只是一個示意的程序。p是指向基類的指針變量,指向new開辟的動態(tài)存儲空間,希望用detele釋放p所指向的空間。但運行結果為executingPointdestructor表示只執(zhí)行了基類Point的析構函數(shù),而沒有執(zhí)行派生類Circle的析構函數(shù)。原因是以前介紹過的。如果希望能執(zhí)行派生類Circle的析構函數(shù),可以將基類的析構函數(shù)聲明為虛析構函數(shù),如virtual~Point(){cout<<″executingPointdestructor″<<endl;}程序其他部分不改動,再運行程序,結果為executingCircledestructorexecutingPointdestructor先調(diào)用了派生類的析構函數(shù),再調(diào)用了基類的析構函數(shù),符合人們的愿望。當基類的析構函數(shù)為虛函數(shù)時,無論指針指的是同一類族中的哪一個類對象,系統(tǒng)會采用動態(tài)關聯(lián),調(diào)用相應的析構函數(shù),對該對象進行清理工作。如果將基類的析構函數(shù)聲明為虛函數(shù)時,由該基類所派生的所有派生類的析構函數(shù)也都自動成為虛函數(shù),即使派生類的析構函數(shù)與基類的析構函數(shù)名字不相同。最好把基類的析構函數(shù)聲明為虛函數(shù)。這將使所有派生類的析構函數(shù)自動成為虛函數(shù)。這樣,如果程序中顯式地用了delete運算符準備刪除一個對象,而delete運算符的操作對象用了指向派生類對象的基類指針,則系統(tǒng)會調(diào)用相應類的析構函數(shù)。虛析構函數(shù)的概念和用法很簡單,但它在面向對象程序設計中卻是很重要的技巧。專業(yè)人員一般都習慣聲明虛析構函數(shù),即使基類并不需要析構函數(shù),也顯式地定義一個函數(shù)體為空的虛析構函數(shù),以保證在撤銷動態(tài)分配空間時能得到正確的處理。構造函數(shù)不能聲明為虛函數(shù)。這是因為在執(zhí)行構造函數(shù)時類對象還未完成建立過程,當然談不上函數(shù)與類對象的綁定。有時在基類中將某一成員函數(shù)定為虛函數(shù),并不是基類本身的要求,而是考慮到派生類的需要,在基類中預留了一個函數(shù)名,具體功能留給派生類根據(jù)需要去定義。例如在本章的例6.1程序中,基類Point中沒有求面積的area函數(shù),因為“點”是沒有面積的,也就是說,基類本身不需要這個函數(shù),所以在例6.1程序中的Point類中沒有定義area函數(shù)。但是,在其直接派生類Circle和間接派生類Cylinder中都需要有area函數(shù),而且這兩個area函數(shù)的功能不同,一個是求圓面積,一個是求圓柱體表面積。
6.4純虛函數(shù)與抽象類
6.4.1純虛函數(shù)這種情況下應當將area聲明為虛函數(shù)?可以在基類Point中加一個area函數(shù),并聲明為虛函數(shù):virtualfloatarea()const{return0;}其返回值為0,表示“點”是沒有面積的。其實,在基類中并不使用這個函數(shù),其返回值也是沒有意義的。為簡化,可以不寫出這種無意義的函數(shù)體,只給出函數(shù)的原型,并在后面加上“=0”,如virtualfloatarea()const=0;//純虛函數(shù)這就將area聲明為一個純虛函數(shù)(purevirtualfunction)。純虛函數(shù)是在聲明虛函數(shù)時被“初始化”為0的函數(shù)。聲明純虛函數(shù)的一般形式是virtual函數(shù)類型函數(shù)名(參數(shù)表列)=0;注意:①純虛函數(shù)沒有函數(shù)體;②最后面的“=0”并不表示函數(shù)返回值為0,它只起形式上的作用,告訴編譯系統(tǒng)“這是純虛函數(shù)”;③這是一個聲明語句,最后應有分號。純虛函數(shù)只有函數(shù)的名字而不具備函數(shù)的功能,不能被調(diào)用。它只是通知編譯系統(tǒng):“在這里聲明一個虛函數(shù),留待派生類中定義”。在派生類中對此函數(shù)提供定義后,它才能具備函數(shù)的功能,可被調(diào)用。純虛函數(shù)的作用是在基類中為其派生類保留一個函數(shù)的名字,以便派生類根據(jù)需要對它進行定義。如果在基類中沒有保留函數(shù)名字,則無法實現(xiàn)多態(tài)性。如果在一個類中聲明了純虛函數(shù),而在其派生類中沒有對該函數(shù)定義,則該虛函數(shù)在派生類中仍然為純虛函數(shù)。如果聲明了一個類,一般可以用它定義對象。但是在面向對象程序設計中,往往有一些類,它們不用來生成對象。定義這些類的惟一目的是用它作為基類去建立派生類。它們作為一種基本類型提供給用戶,用戶在這個基礎上根據(jù)自己的需要定義出功能各異的派生類。用這些派生類去建立對象。在開發(fā)一個大的軟件時,決不會從頭到尾都由自己編寫程序代碼,而是充分利用已有資源(例如類庫)作為自己工作的基礎。這種不用來定義對象而只作為一種基本類型用作繼承的類,稱為抽象類(abstractclass),由于它常用作基類,通常稱為抽象基類(abstractbaseclass)。6.4.2抽象類凡是包含純虛函數(shù)的類都是抽象類。因為純虛函數(shù)是不能被調(diào)用的,包含純虛函數(shù)的類是無法建立對象的。抽象類的作用是作為一個類族的共同基類,或者說,為一個類族提供一個公共接口。一個類層次結構中當然也可不包含任何抽象類,每一層次的類都是實際可用的,可以用來建立對象的。但是,許多好的面向對象的系統(tǒng),其層次結構的頂部是一個抽象類,甚至頂部有好幾層都是抽象類。如果在抽象類所派生出的新類中對基類的所有純虛函數(shù)進行了定義,那么這些函數(shù)就被賦予了功能,可以被調(diào)用。這個派生類就不是抽象類,而是可以用來定義對象的具體類(concreteclass)。如果在派生類中沒有對所有純虛函數(shù)進行定義,則此派生類仍然是抽象類,不能用來定義對象。雖然抽象類不能定義對象(或者說抽象類不能實例化),但是可以定義指向抽象類數(shù)據(jù)的指針變量。當派生類成為具體類之后,就可以用這種指針指向派生類對象,然后通過該指針調(diào)用虛函數(shù),實現(xiàn)多態(tài)性的操作。例6.4
虛函數(shù)和抽象基類的應用。在例6.1介紹了以Point為基類的點—圓—圓柱體類的層次結構?,F(xiàn)在要對它進行改寫,在程序中使用虛函數(shù)和抽象基類。類的層次結構的頂層是抽象基類Shape(形狀)。Point(點),Circle(圓),Cylinder(圓柱體)都是Shape類的直接派生類和間接派生類。下面是一個完整的程序:6.4.3應用實例第(1)部分#include<iostream>usingnamespacestd;//聲明抽象基類ShapeclassShape{public:virtualfloatarea()const{return0.0;}//虛函數(shù)
virtualfloatvolume()const{return0.0;}//虛函數(shù)
virtualvoidshapeName()const=0;//純虛函數(shù)};第(2)部分//聲明Point類classPoint:publicShape//Point是Shape的公用派生類{public:
Point(float=0,float=0);voidsetPoint(float,float);floatgetX()const{returnx;}floatgetY()const{returny;}virtualvoidshapeName()const{cout<<″Point:″;}//對虛函數(shù)進行再定義
friendostream&operator<<(ostream&,constPoint&);protected:floatx,y;};//定義Point類成員函數(shù)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;}第(3)部分//聲明Circle類classCircle:publicPoint{public:
Circle(floatx=0,floaty=0,floatr=0);voidsetRadius(float);floatgetRadius()const;virtualfloatarea()const;virtualvoidshapeName()const{cout<<″Circle:″;}//對虛函數(shù)進行再定義
friendostream&operator<<(ostream&,constCircle&);protected:floatradius;};//聲明Circle類成員函數(shù)Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}voidCircle::setRadius(float
r):radius(r){}floatCircle::getRadius()const{returnradius;}floatCircle::area()const{return3.14159*radius*radius;}ostream&operator<<(ostream&output,constCircle&c){output<<″[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius;returnoutput;}第(4)部分//聲明Cylinder類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;}floatCylinder::volume()const{returnCircle::area()*height;}ostream&operator<<(ostream&output,constCylinder&cy){output<<″[″<<cy.x<<″,″<<cy.y<<″],r=″<<cy.radius<<″,h=″<<cy.height;returnoutput;}第(5)部分//main函數(shù)intmain(){Pointpoint(3.2,4.5);//建立Point類對象pointCirclecircle(2.4,1.2,5.6);//建立Circle類對象circleCylindercylinder(3.5,6.4,5.2,10.5);//建立Cylinder類對象cylinder
point.shapeName();//靜態(tài)關聯(lián)
cout<<point<<endl;
circle.shapeName();//靜態(tài)關聯(lián)
cout<<circle<<endl;
cylinder.shapeName();//靜態(tài)關聯(lián)
cout<<cylinder<<endl<<endl;Shape*pt;//定義基類指針pt=&point;//指針指向Point類對象
pt->shapeName();
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 體驗店行業(yè)市場營銷總結
- 2025-2030全球無DEHP分隔膜無針輸液接頭行業(yè)調(diào)研及趨勢分析報告
- 2025-2030全球基因組注釋服務行業(yè)調(diào)研及趨勢分析報告
- 2025-2030全球酚醛彩鋼板行業(yè)調(diào)研及趨勢分析報告
- 2025年全球及中國隧道安全監(jiān)測系統(tǒng)行業(yè)頭部企業(yè)市場占有率及排名調(diào)研報告
- 2025-2030全球燃氣輪機仿真軟件行業(yè)調(diào)研及趨勢分析報告
- 2025年全球及中國自動水力平衡閥行業(yè)頭部企業(yè)市場占有率及排名調(diào)研報告
- 2025-2030全球辦公室文件柜行業(yè)調(diào)研及趨勢分析報告
- 2025年全球及中國4-苯氧基苯酚行業(yè)頭部企業(yè)市場占有率及排名調(diào)研報告
- 2025-2030全球太空級電機控制器行業(yè)調(diào)研及趨勢分析報告
- 護理人文知識培訓課件
- 建筑工程施工安全管理課件
- 2025年春新人教版數(shù)學七年級下冊教學課件 7.2.3 平行線的性質(第1課時)
- 安徽省合肥市2025年高三第一次教學質量檢測地理試題(含答案)
- 2025年新合同管理工作計劃
- 統(tǒng)編版八年級下冊語文第三單元名著導讀《經(jīng)典常談》閱讀指導 學案(含練習題及答案)
- 風光儲儲能項目PCS艙、電池艙吊裝方案
- 產(chǎn)業(yè)鏈競爭關聯(lián)度
- TTJSFB 002-2024 綠色融資租賃項目評價指南
- 高考地理一輪復習學案+區(qū)域地理填圖+亞洲
- 全新車位轉讓協(xié)議模板下載(2024版)
評論
0/150
提交評論