版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第6章多態(tài)性與虛函數(shù)6.1多態(tài)性的概念6.2一個(gè)典型的例子6.3虛函數(shù)6.4純虛函數(shù)與抽象類
多態(tài)性(polymorphism)是面向?qū)ο蟪绦蛟O(shè)計(jì)的重要特征。利用多態(tài)性可以設(shè)計(jì)和實(shí)現(xiàn)一個(gè)易于擴(kuò)展的系統(tǒng)。C++程序設(shè)計(jì)中,多態(tài)性是指具有不同功能的函數(shù),可用同一個(gè)函數(shù)名,從而實(shí)現(xiàn)用一個(gè)函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。面向?qū)ο蠓椒ㄖ?,多態(tài)性描述是:向不同的對(duì)象發(fā)送同一個(gè)消息,不同的對(duì)象在接收時(shí)會(huì)產(chǎn)生不同的行為(即方法)。即,每個(gè)對(duì)象可用自己的方式去響應(yīng)相同的消息。從系統(tǒng)實(shí)現(xiàn)的角度,多態(tài)性分為兩類:靜態(tài)多態(tài)性和動(dòng)態(tài)多態(tài)性。靜態(tài)多態(tài)性,在程序編譯時(shí)系統(tǒng)就能決定調(diào)用的是哪個(gè)函數(shù),因此靜態(tài)多態(tài)性又稱編譯時(shí)的多態(tài)性。靜態(tài)多態(tài)性是通過函數(shù)重載實(shí)現(xiàn)(運(yùn)算符重載實(shí)質(zhì)上也是函數(shù)重載)。6.1多態(tài)性的概念動(dòng)態(tài)多態(tài)性在程序運(yùn)行過程中,才動(dòng)態(tài)確定操作所針對(duì)的對(duì)象,又稱運(yùn)行時(shí)的多態(tài)性。動(dòng)態(tài)多態(tài)性通過虛函數(shù)(virtualfunction)實(shí)現(xiàn)。
問題:當(dāng)一個(gè)基類被繼承為不同派生類時(shí),各派生類可以使用與基類成員相同的成員名,在運(yùn)行時(shí)如用同一個(gè)成員名,調(diào)用類對(duì)象的成員,會(huì)調(diào)用哪個(gè)對(duì)象的成員?即:通過繼承而產(chǎn)生了相關(guān)的不同派生類,與基類成員同名的成員,在不同派生類中有不同的含義。也可以說,多態(tài)性是“一個(gè)接口,多種方法”。6.2一個(gè)典型的例子例6.1
先建立一個(gè)Point(點(diǎn))類,包含數(shù)據(jù)成員x,y(坐標(biāo)點(diǎn))。以它為基類,派生出一個(gè)Circle(圓)類,增加數(shù)據(jù)成員r(半徑)。再以Circle類為直接基類,派生出一個(gè)Cylinder(圓柱體)類,再增加數(shù)據(jù)成員h(高)。要求:編寫程序,重載運(yùn)算符“<<”和“>>”,使之能用于輸出以上類對(duì)象。對(duì)一個(gè)比較大的程序,應(yīng)當(dāng)分若干步驟進(jìn)行。先聲明基類,再聲明派生類,逐級(jí)進(jìn)行,分步調(diào)試。(1)聲明基類Point類#include<iostream>classPoint{public:Point(floatx=0,floaty=0);//有默認(rèn)參數(shù)的構(gòu)造函數(shù)voidsetPoint(float,float);//設(shè)置坐標(biāo)值
floatgetX()const{returnx;}//讀x坐標(biāo)
floatgetY()const{returny;}//讀y坐標(biāo)
friendostream&operator<<(ostream&,constPoint&);//重載“<<”protected://受保護(hù)成員
floatx,y;};Point::Point(floata,floatb)//對(duì)x,y初始化{x=a;y=b;}voidPoint::setPoint(floata,floatb)//為x,y賦新值{x=a;y=b;}//重載運(yùn)算符“<<”,使之能輸出點(diǎn)的坐標(biāo)ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″<<endl;returnoutput;}現(xiàn)在對(duì)上面寫的基類聲明進(jìn)行調(diào)試,檢查它是否有錯(cuò),為此要寫出main函數(shù)。實(shí)際上它是一個(gè)測(cè)試程序。intmain(){Pointp(3.5,6.4);//建立Point類對(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)}程序編譯通過,運(yùn)行結(jié)果:x=3.5,y=6.4p(new):[8.5,6.8]測(cè)試程序檢查了基類中各函數(shù)的功能,以及運(yùn)算符重載的作用。(2)聲明派生類CircleclassCircle:publicPoint//circle是Point類的公用派生類{public:Circle(floatx=0,floaty=0,floatr=0);//構(gòu)造函數(shù)
voidsetRadius(float);//設(shè)置半徑值
floatgetRadius()const;//讀取半徑值
floatarea()const;//計(jì)算圓面積
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;}//重載運(yùn)算符“<<”,使之按規(guī)定的形式輸出圓的信息ostream&operator<<(ostream&output,constCircle&c){output<<″Center=[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius<<″,area=″<<c.area()<<endl;returnoutput;}為了測(cè)試以上Circle類的定義,寫出下面的主函數(shù):intmain(){Circlec(3.5,6.4,5.2);//建立Circle類對(duì)象c,并給定圓心坐標(biāo)和半徑
cout<<″originalcircle:\\nx=″<<c.getX()<<″,y=″<<c.getY()<<″,r=″<<c.getRadius()<<″,area=″<<c.area()<<endl;c.setRadius(7.5);//設(shè)置半徑值
c.setPoint(5,5);//設(shè)置圓心坐標(biāo)值x,y
cout<<″newcircle:\\n″<<c;//用重載“<<”輸出圓對(duì)象的信息
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);//構(gòu)造函數(shù)
voidsetHeight(float);//設(shè)置圓柱高
floatgetHeight()const;//讀取圓柱高
floatarea()const;//計(jì)算圓表面積
floatvolume()const;//計(jì)算圓柱體積
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類對(duì)象cy1
cout<<″x=″<<cy1.getX()<<″,y=″<<cy1.getY()<<″,r=″<<cy1.getRadius()<<″,h=″<<cy1.getHeight()<<″area=″<<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<<″newcylinder:″<<cy1;//用重載<<輸出cy1的數(shù)據(jù)
Point&pRef=cy1;//pRef是Point類對(duì)象的引用變量
cout<<″pRefasaPoint:″<<pRef;//pRef作為一個(gè)“點(diǎn)”輸出
Circle&cRef=cy1;//cRef是Circle類對(duì)象的引用變量
cout<<″cRefasaCircle:″<<cRef;//cRef作為一個(gè)“圓”輸出
return0;}運(yùn)行結(jié)果:originalcylinder:(輸出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)newcylinder:(輸出cy1的新值)Center=[5,5],r=7.5,h=15(以[5,5]形式輸出圓心坐標(biāo))area=1060.29,volume=2650.72(圓柱表面積area和體積volume)pRefasaPoint:[5,5](pRef作為一個(gè)“點(diǎn)”輸出)cRefasaCircle:Center=[5,5],r=7.5,area=176.714(cRef作為“圓”輸出)本例中存在靜態(tài)多態(tài)性,這是運(yùn)算符重載引起的??梢?,編譯時(shí)編譯系統(tǒng)即可以判定應(yīng)調(diào)用哪個(gè)重載運(yùn)算符函數(shù)。6.3.1虛函數(shù)的作用在類繼承層次結(jié)構(gòu)中,在不同層次,可以出現(xiàn)名字相同、參數(shù)個(gè)數(shù)和類型都相同而功能不同的函數(shù)。編譯系統(tǒng)按照同名覆蓋原則,決定所調(diào)用的對(duì)象。在例6.1中,用cy1.area()調(diào)用的是派生類Cylinder中的成員函數(shù)area。如想調(diào)用cy1中的、直接基類Circle的area函數(shù),應(yīng)表示為:cy1.Circle::area()。用這種方法來區(qū)分兩個(gè)同名的函數(shù)。
設(shè)想:能否用同一個(gè)調(diào)用形式,既能調(diào)用派生類,又能調(diào)用基類的同名函數(shù)?在程序中,不通過不同的對(duì)象名,去調(diào)用不同派生層次中的同名函數(shù),而是通過指針調(diào)用它們。如:用同一個(gè)語(yǔ)句“pt->display();”可以調(diào)用不同派生層次中的display函數(shù),只需在調(diào)用前,給指針變量pt賦以不同的值(使之指向不同的類對(duì)象)即可。6.3虛函數(shù)
C++中,用虛函數(shù)來解決此問題。虛函數(shù)作用是:允許在派生類中重新定義與基類同名的函數(shù),并可以通過基類指針或引用,來訪問基類和派生類中的同名函數(shù)。例6.2
基類與派生類中有同名函數(shù)。#include<iostream>#include<string>usingnamespacestd;classStudent{public:
Student(int,string,float);//聲明構(gòu)造函數(shù)
voiddisplay();//聲明輸出函數(shù)
protected://受保護(hù)成員,派生類可以訪問
intnum;stringname;floatscore;};Student::Student(intn,stringnam,floats)//定義構(gòu)造函數(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);//聲明構(gòu)造函數(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類對(duì)象stud1Graduategrad1(2001,″Wang″,98.5,563.5);//定義Graduate類對(duì)象Student*pt=&stud1;//定義指向基類對(duì)象的指針變量ptpt->display();pt=&grad1;pt->display();return0;}運(yùn)行結(jié)果:num:1001(stud1的數(shù)據(jù))name:Liscore:87.5num:2001(grad1中基類部分的數(shù)據(jù))name:wangscore:98.5下面進(jìn)行修改:在Student類中聲明display函數(shù)時(shí),在最左面加一個(gè)關(guān)鍵字virtual,即:virtualvoiddisplay();即把Student類的display函數(shù)聲明為虛函數(shù)。程序其他部分都不改動(dòng)。再編譯和運(yùn)行程序,運(yùn)行結(jié)果:num:1001(stud1的數(shù)據(jù))name:Liscore:87.5num:2001(grad1中基類部分的數(shù)據(jù))name:wangscore:98.5pay=1200(這一項(xiàng)以前是沒有的)由虛函數(shù)實(shí)現(xiàn)的動(dòng)態(tài)多態(tài)性就是:同一類族中不同類的對(duì)象,對(duì)同一函數(shù)調(diào)用作出不同的響應(yīng)。虛函數(shù)使用方法:(1)在基類用virtual聲明成員函數(shù)為虛函數(shù)。這樣就可以在派生類中重新定義此函數(shù),為它賦予新的功能,并能方便地被調(diào)用。在類外定義虛函數(shù)時(shí),不必再加virtual。(2)
在派生類中重新定義此函數(shù),要求函數(shù)名、函數(shù)類型、函數(shù)參數(shù)個(gè)數(shù)和類型全部與基類的虛函數(shù)相同,并根據(jù)派生類的需要重新定義函數(shù)體。C++規(guī)定,當(dāng)一個(gè)成員函數(shù)被聲明為虛函數(shù)后,其派生類中的同名函數(shù)都自動(dòng)成為虛函數(shù)。因此在派生類重新聲明該虛函數(shù)時(shí),可以加virtual,也可以不加,但習(xí)慣上一般在每一層聲明該函數(shù)時(shí)都加virtual,使程序更加清晰。如果在派生類中沒有對(duì)基類的虛函數(shù)重新定義,則派生類簡(jiǎn)單地繼承其直接基類的虛函數(shù)。(3)定義一個(gè)指向基類對(duì)象的指針變量,并使它指向同一類族中需要調(diào)用該函數(shù)的對(duì)象。(4)通過該指針變量調(diào)用此虛函數(shù),此時(shí)調(diào)用的就是指針變量指向的對(duì)象的同名函數(shù)。通過虛函數(shù)與指向基類對(duì)象的指針變量的配合使用,就能方便地調(diào)用同一類族中、不同類的同名函數(shù),只要先用基類指針指向即可。如果指針不斷指向同一類族中不同類的對(duì)象,就能不斷地調(diào)用這些對(duì)象中的同名函數(shù)。說明;有時(shí),在基類中定義的非虛函數(shù),會(huì)在派生類中被重新定義(如例6.1中的area函數(shù)),如果用基類指針調(diào)用該成員函數(shù),則系統(tǒng)會(huì)調(diào)用對(duì)象中基類部分的成員函數(shù);如果用派生類指針調(diào)用該成員函數(shù),則系統(tǒng)會(huì)調(diào)用派生類對(duì)象中的成員函數(shù),這并不是多態(tài)性行為(使用的是不同類型的指針),沒有用到虛函數(shù)的功能。函數(shù)重載,處理的是同一層次上的同名函數(shù)問題,而虛函數(shù)處理的是不同派生層次上的同名函數(shù)問題,前者是橫向重載,后者可以理解為縱向重載。與重載不同的是:同一類族的虛函數(shù)的首部是相同的,而函數(shù)重載時(shí)函數(shù)的首部是不同的(參數(shù)個(gè)數(shù)或類型不同)。編譯系統(tǒng)要根據(jù)已有的信息,對(duì)同名函數(shù)的調(diào)用作出判斷。對(duì)于調(diào)用同一類族中的虛函數(shù),應(yīng)當(dāng)在調(diào)用時(shí)用一定的方式告訴編譯系統(tǒng),你要調(diào)用的是哪個(gè)類對(duì)象中的函數(shù)。這樣編譯系統(tǒng)在對(duì)程序進(jìn)行編譯時(shí),即能確定調(diào)用的是哪個(gè)類對(duì)象中的函數(shù)。確定調(diào)用的具體對(duì)象的過程稱為關(guān)聯(lián)(binding)。在這里是指把一個(gè)函數(shù)名與一個(gè)類對(duì)象捆綁在一起,建立關(guān)聯(lián)。一般地說,關(guān)聯(lián)指把一個(gè)標(biāo)識(shí)符和一個(gè)存儲(chǔ)地址聯(lián)系起來。前面所提到的函數(shù)重載和通過對(duì)象名調(diào)用的虛函數(shù),在編譯時(shí)即可確定其調(diào)用的虛函數(shù)屬于哪一個(gè)類,其過程稱為靜態(tài)關(guān)聯(lián)(staticbinding),由于是在運(yùn)行前進(jìn)行關(guān)聯(lián)的,故又稱為早期關(guān)聯(lián)(earlybinding)。函數(shù)重載屬靜態(tài)關(guān)聯(lián)。6.3.2靜態(tài)關(guān)聯(lián)與動(dòng)態(tài)關(guān)聯(lián)
在上一小節(jié)程序中看到了怎樣使用虛函數(shù),在調(diào)用虛函數(shù)時(shí)并沒有指定對(duì)象名,那么系統(tǒng)是怎樣確定關(guān)聯(lián)的呢?是通過基類指針與虛函數(shù)的結(jié)合來實(shí)現(xiàn)多態(tài)性的。先定義了一個(gè)指向基類的指針變量,并使它指向相應(yīng)的類對(duì)象,然后通過這個(gè)基類指針去調(diào)用虛函數(shù)(例如“pt->display()”)。顯然,對(duì)這樣的調(diào)用方式,編譯系統(tǒng)在編譯該行時(shí)是無法確定調(diào)用哪一個(gè)類對(duì)象的虛函數(shù)的。因?yàn)榫幾g只作靜態(tài)的語(yǔ)法檢查,光從語(yǔ)句形式是無法確定調(diào)用對(duì)象的。在這樣的情況下,編譯系統(tǒng)把它放到運(yùn)行階段處理,在運(yùn)行階段確定關(guān)聯(lián)關(guān)系。在運(yùn)行階段,基類指針變量先指向了某一個(gè)類對(duì)象,然后通過此指針變量調(diào)用該對(duì)象中的函數(shù)。此時(shí)調(diào)用哪一個(gè)對(duì)象的函數(shù)無疑是確定的。例如,先使pt指向grad1,再執(zhí)行“pt->display()”,當(dāng)然是調(diào)用grad1中的display函數(shù)。由于是在運(yùn)行階段把虛函數(shù)和類對(duì)象“綁定”在一起的,因此,此過程稱為動(dòng)態(tài)關(guān)聯(lián)(dynamicbinding)。這種多態(tài)性是動(dòng)態(tài)的多態(tài)性,即運(yùn)行階段的多態(tài)性。在運(yùn)行階段,指針可以先后指向不同的類對(duì)象,從而調(diào)用同一類族中不同類的虛函數(shù)。由于動(dòng)態(tài)關(guān)聯(lián)是在編譯以后的運(yùn)行階段進(jìn)行的,因此也稱為滯后關(guān)聯(lián)(latebinding)。使用虛函數(shù)時(shí),有兩點(diǎn)要注意:(1)只能用virtual聲明類的成員函數(shù),使它成為虛函數(shù),而不能將類外的普通函數(shù)聲明為虛函數(shù)。因?yàn)樘摵瘮?shù)的作用是允許在派生類中對(duì)基類的虛函數(shù)重新定義。顯然,它只能用于類的繼承層次結(jié)構(gòu)中。(2)一個(gè)成員函數(shù)被聲明為虛函數(shù)后,在同一類族中的類就不能再定義一個(gè)非virtual的但與該虛函數(shù)具有相同的參數(shù)(包括個(gè)數(shù)和類型)和函數(shù)返回值類型的同名函數(shù)。根據(jù)什么考慮是否把一個(gè)成員函數(shù)聲明為虛函數(shù)呢?要考慮以下幾點(diǎn):(1)首先看成員函數(shù)所在的類是否會(huì)作為基類。然后看成員函數(shù)在類的繼承后有無可能被更改功能,如果希望更改其功能的,一般應(yīng)該將它聲明為虛函數(shù)。6.3.3在什么情況下應(yīng)當(dāng)聲明虛函數(shù)(2)如果成員函數(shù)在類被繼承后功能不需修改,或派生類用不到該函數(shù),則不要把它聲明為虛函數(shù)。不要僅僅考慮到要作為基類而把類中的所有成員函數(shù)都聲明為虛函數(shù)。(3)應(yīng)考慮對(duì)成員函數(shù)的調(diào)用是通過對(duì)象名還是通過基類指針或引用去訪問,如果是通過基類指針或引用去訪問的,則應(yīng)當(dāng)聲明為虛函數(shù)。(4)有時(shí),在定義虛函數(shù)時(shí),并不定義其函數(shù)體,即函數(shù)體是空的。它的作用只是定義了一個(gè)虛函數(shù)名,具體功能留給派生類去添加。在12.4節(jié)中將詳細(xì)討論此問題。說明:使用虛函數(shù),系統(tǒng)要有一定的空間開銷。當(dāng)一個(gè)類帶有虛函數(shù)時(shí),編譯系統(tǒng)會(huì)為該類構(gòu)造一個(gè)虛函數(shù)表(virtualfunctiontable,簡(jiǎn)稱vtable),它是一個(gè)指針數(shù)組,存放每個(gè)虛函數(shù)的入口地址。系統(tǒng)在進(jìn)行動(dòng)態(tài)關(guān)聯(lián)時(shí)的時(shí)間開銷是很少的,因此,多態(tài)性是高效的。析構(gòu)函數(shù)的作用是在對(duì)象撤銷之前做必要的“清理現(xiàn)場(chǎng)”的工作。當(dāng)派生類的對(duì)象從內(nèi)存中撤銷時(shí)一般先調(diào)用派生類的析構(gòu)函數(shù),然后再調(diào)用基類的析構(gòu)函數(shù)。但是,如果用new運(yùn)算符建立了臨時(shí)對(duì)象,若基類中有析構(gòu)函數(shù),并且定義了一個(gè)指向該基類的指針變量。在程序用帶指針參數(shù)的delete運(yùn)算符撤銷對(duì)象時(shí),會(huì)發(fā)生一個(gè)情況:系統(tǒng)會(huì)只執(zhí)行基類的析構(gòu)函數(shù),而不執(zhí)行派生類的析構(gòu)函數(shù)。6.3.4虛析構(gòu)函數(shù)例6.3
基類中有非虛析構(gòu)函數(shù)時(shí)的執(zhí)行情況。#include<iostream>usingnamespacestd;classPoint//定義基類Point類{public:Point(){}//Point類構(gòu)造函數(shù)~Point(){cout<<″e(cuò)xecutingPointdestructor″<<endl;}//Point類析構(gòu)函數(shù)};classCircle:publicPoint//定義派生類Circle類{public:Circle(){}//Circle類構(gòu)造函數(shù)~Circle(){cout<<″e(cuò)xecutingCircledestructor″<<endl;}//Circle類析構(gòu)函數(shù)
private:
intradius;};intmain(){Point*p=newCircle;//用new開辟動(dòng)態(tài)存儲(chǔ)空間deletep;//用delete釋放動(dòng)態(tài)存儲(chǔ)空間return0;}這只是一個(gè)示意的程序。p是指向基類的指針變量,指向new開辟的動(dòng)態(tài)存儲(chǔ)空間,希望用detele釋放p所指向的空間。但運(yùn)行結(jié)果為executingPointdestructor表示只執(zhí)行了基類Point的析構(gòu)函數(shù),而沒有執(zhí)行派生類Circle的析構(gòu)函數(shù)。原因是以前介紹過的。如果希望能執(zhí)行派生類Circle的析構(gòu)函數(shù),可以將基類的析構(gòu)函數(shù)聲明為虛析構(gòu)函數(shù),如virtual~Point(){cout<<″e(cuò)xecutingPointdestructor″<<endl;}程序其他部分不改動(dòng),再運(yùn)行程序,結(jié)果為executingCircledestructorexecutingPointdestructor先調(diào)用了派生類的析構(gòu)函數(shù),再調(diào)用了基類的析構(gòu)函數(shù),符合人們的愿望。當(dāng)基類的析構(gòu)函數(shù)為虛函數(shù)時(shí),無論指針指的是同一類族中的哪一個(gè)類對(duì)象,系統(tǒng)會(huì)采用動(dòng)態(tài)關(guān)聯(lián),調(diào)用相應(yīng)的析構(gòu)函數(shù),對(duì)該對(duì)象進(jìn)行清理工作。如果將基類的析構(gòu)函數(shù)聲明為虛函數(shù)時(shí),由該基類所派生的所有派生類的析構(gòu)函數(shù)也都自動(dòng)成為虛函數(shù),即使派生類的析構(gòu)函數(shù)與基類的析構(gòu)函數(shù)名字不相同。最好把基類的析構(gòu)函數(shù)聲明為虛函數(shù)。這將使所有派生類的析構(gòu)函數(shù)自動(dòng)成為虛函數(shù)。這樣,如果程序中顯式地用了delete運(yùn)算符準(zhǔn)備刪除一個(gè)對(duì)象,而delete運(yùn)算符的操作對(duì)象用了指向派生類對(duì)象的基類指針,則系統(tǒng)會(huì)調(diào)用相應(yīng)類的析構(gòu)函數(shù)。虛析構(gòu)函數(shù)的概念和用法很簡(jiǎn)單,但它在面向?qū)ο蟪绦蛟O(shè)計(jì)中卻是很重要的技巧。專業(yè)人員一般都習(xí)慣聲明虛析構(gòu)函數(shù),即使基類并不需要析構(gòu)函數(shù),也顯式地定義一個(gè)函數(shù)體為空的虛析構(gòu)函數(shù),以保證在撤銷動(dòng)態(tài)分配空間時(shí)能得到正確的處理。構(gòu)造函數(shù)不能聲明為虛函數(shù)。這是因?yàn)樵趫?zhí)行構(gòu)造函數(shù)時(shí)類對(duì)象還未完成建立過程,當(dāng)然談不上函數(shù)與類對(duì)象的綁定。有時(shí)在基類中將某一成員函數(shù)定為虛函數(shù),并不是基類本身的要求,而是考慮到派生類的需要,在基類中預(yù)留了一個(gè)函數(shù)名,具體功能留給派生類根據(jù)需要去定義。例如在本章的例6.1程序中,基類Point中沒有求面積的area函數(shù),因?yàn)椤包c(diǎn)”是沒有面積的,也就是說,基類本身不需要這個(gè)函數(shù),所以在例6.1程序中的Point類中沒有定義area函數(shù)。但是,在其直接派生類Circle和間接派生類Cylinder中都需要有area函數(shù),而且這兩個(gè)area函數(shù)的功能不同,一個(gè)是求圓面積,一個(gè)是求圓柱體表面積。
6.4純虛函數(shù)與抽象類
6.4.1純虛函數(shù)這種情況下應(yīng)當(dāng)將area聲明為虛函數(shù)?可以在基類Point中加一個(gè)area函數(shù),并聲明為虛函數(shù):virtualfloatarea()const{return0;}其返回值為0,表示“點(diǎn)”是沒有面積的。其實(shí),在基類中并不使用這個(gè)函數(shù),其返回值也是沒有意義的。為簡(jiǎn)化,可以不寫出這種無意義的函數(shù)體,只給出函數(shù)的原型,并在后面加上“=0”,如virtualfloatarea()const=0;//純虛函數(shù)這就將area聲明為一個(gè)純虛函數(shù)(purevirtualfunction)。純虛函數(shù)是在聲明虛函數(shù)時(shí)被“初始化”為0的函數(shù)。聲明純虛函數(shù)的一般形式是virtual函數(shù)類型函數(shù)名(參數(shù)表列)=0;注意:①純虛函數(shù)沒有函數(shù)體;②最后面的“=0”并不表示函數(shù)返回值為0,它只起形式上的作用,告訴編譯系統(tǒng)“這是純虛函數(shù)”;③這是一個(gè)聲明語(yǔ)句,最后應(yīng)有分號(hào)。純虛函數(shù)只有函數(shù)的名字而不具備函數(shù)的功能,不能被調(diào)用。它只是通知編譯系統(tǒng):“在這里聲明一個(gè)虛函數(shù),留待派生類中定義”。在派生類中對(duì)此函數(shù)提供定義后,它才能具備函數(shù)的功能,可被調(diào)用。純虛函數(shù)的作用是在基類中為其派生類保留一個(gè)函數(shù)的名字,以便派生類根據(jù)需要對(duì)它進(jìn)行定義。如果在基類中沒有保留函數(shù)名字,則無法實(shí)現(xiàn)多態(tài)性。如果在一個(gè)類中聲明了純虛函數(shù),而在其派生類中沒有對(duì)該函數(shù)定義,則該虛函數(shù)在派生類中仍然為純虛函數(shù)。如果聲明了一個(gè)類,一般可以用它定義對(duì)象。但是在面向?qū)ο蟪绦蛟O(shè)計(jì)中,往往有一些類,它們不用來生成對(duì)象。定義這些類的惟一目的是用它作為基類去建立派生類。它們作為一種基本類型提供給用戶,用戶在這個(gè)基礎(chǔ)上根據(jù)自己的需要定義出功能各異的派生類。用這些派生類去建立對(duì)象。在開發(fā)一個(gè)大的軟件時(shí),決不會(huì)從頭到尾都由自己編寫程序代碼,而是充分利用已有資源(例如類庫(kù))作為自己工作的基礎(chǔ)。這種不用來定義對(duì)象而只作為一種基本類型用作繼承的類,稱為抽象類(abstractclass),由于它常用作基類,通常稱為抽象基類(abstractbaseclass)。6.4.2抽象類凡是包含純虛函數(shù)的類都是抽象類。因?yàn)榧兲摵瘮?shù)是不能被調(diào)用的,包含純虛函數(shù)的類是無法建立對(duì)象的。抽象類的作用是作為一個(gè)類族的共同基類,或者說,為一個(gè)類族提供一個(gè)公共接口。一個(gè)類層次結(jié)構(gòu)中當(dāng)然也可不包含任何抽象類,每一層次的類都是實(shí)際可用的,可以用來建立對(duì)象的。但是,許多好的面向?qū)ο蟮南到y(tǒng),其層次結(jié)構(gòu)的頂部是一個(gè)抽象類,甚至頂部有好幾層都是抽象類。如果在抽象類所派生出的新類中對(duì)基類的所有純虛函數(shù)進(jìn)行了定義,那么這些函數(shù)就被賦予了功能,可以被調(diào)用。這個(gè)派生類就不是抽象類,而是可以用來定義對(duì)象的具體類(concreteclass)。如果在派生類中沒有對(duì)所有純虛函數(shù)進(jìn)行定義,則此派生類仍然是抽象類,不能用來定義對(duì)象。雖然抽象類不能定義對(duì)象(或者說抽象類不能實(shí)例化),但是可以定義指向抽象類數(shù)據(jù)的指針變量。當(dāng)派生類成為具體類之后,就可以用這種指針指向派生類對(duì)象,然后通過該指針調(diào)用虛函數(shù),實(shí)現(xiàn)多態(tài)性的操作。例6.4
虛函數(shù)和抽象基類的應(yīng)用。在例6.1介紹了以Point為基類的點(diǎn)—圓—圓柱體類的層次結(jié)構(gòu)?,F(xiàn)在要對(duì)它進(jìn)行改寫,在程序中使用虛函數(shù)和抽象基類。類的層次結(jié)構(gòu)的頂層是抽象基類Shape(形狀)。Point(點(diǎn)),Circle(圓),Cylinder(圓柱體)都是Shape類的直接派生類和間接派生類。下面是一個(gè)完整的程序:6.4.3應(yīng)用實(shí)例第(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:″;}//對(duì)虛函數(shù)進(jìn)行再定義
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:″;}//對(duì)虛函數(shù)進(jìn)行再定義
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:″;}//對(duì)虛函數(shù)進(jìn)行再定義
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類對(duì)象pointCirclecircle(2.4,1.2,5.6);//建立Circle類對(duì)象circleCylindercylinder(3.5,6.4,5.2,10.5);//建立Cylinder類對(duì)象cylinder
point.shapeName();//靜態(tài)關(guān)聯(lián)
cout<<point<<endl;
circle.shapeName();//靜態(tài)關(guān)聯(lián)
cout<<circle<<endl;
cylinder.shapeName();//靜態(tài)關(guān)聯(lián)
cout<<cylinder<<endl<<endl;Shape*pt;//定義基類指針pt=&point;//指針指向Point類對(duì)象
pt->shapeName();
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 土石方挖掘機(jī)司機(jī)操作安全考核試卷含答案
- 合成氨煤氣化工操作規(guī)范考核試卷含答案
- 瓦斯抽放工崗前安全意識(shí)強(qiáng)化考核試卷含答案
- 液體二氧化碳生產(chǎn)工安全知識(shí)宣貫?zāi)M考核試卷含答案
- 催化重整裝置操作工安全培訓(xùn)測(cè)試考核試卷含答案
- 2024年日照康養(yǎng)職業(yè)學(xué)院輔導(dǎo)員招聘?jìng)淇碱}庫(kù)附答案
- 景泰藍(lán)制胎工發(fā)展趨勢(shì)考核試卷含答案
- 電機(jī)裝配工安全生產(chǎn)意識(shí)測(cè)試考核試卷含答案
- 戲服制作工操作規(guī)范考核試卷含答案
- 耕整地機(jī)械操作工班組評(píng)比測(cè)試考核試卷含答案
- 吉林省梅河口市五中2025-2026學(xué)年高二上學(xué)期期末語(yǔ)文試卷及答案
- 2026遼寧機(jī)場(chǎng)管理集團(tuán)校招面筆試題及答案
- 2026年共青團(tuán)中央所屬單位高校畢業(yè)生公開招聘66人備考題庫(kù)及參考答案詳解
- 2025徽銀金融租賃有限公司社會(huì)招聘筆試歷年典型考題及考點(diǎn)剖析附帶答案詳解
- 2026年遼寧軌道交通職業(yè)學(xué)院?jiǎn)握芯C合素質(zhì)筆試備考題庫(kù)帶答案解析
- 2026年6級(jí)英語(yǔ)模擬真題及答案
- 塔吊運(yùn)行日志
- 里氏硬度計(jì)算表
- 輸電線路基礎(chǔ)知識(shí)輸電線路組成與型式
- GB/T 24128-2009塑料防霉性能試驗(yàn)方法
- 土地買賣合同協(xié)議書模板
評(píng)論
0/150
提交評(píng)論