第六章多態(tài)性與虛函數(shù)_第1頁
第六章多態(tài)性與虛函數(shù)_第2頁
第六章多態(tài)性與虛函數(shù)_第3頁
第六章多態(tài)性與虛函數(shù)_第4頁
第六章多態(tài)性與虛函數(shù)_第5頁
已閱讀5頁,還剩75頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第六章多態(tài)性與虛函數(shù)第一頁,共八十頁,編輯于2023年,星期五封裝性和繼承性為軟件設(shè)計提供了可靠的安全性和良好的結(jié)構(gòu)層次。在此基礎(chǔ)上,多態(tài)性使軟件中的對象行為更加符合軟件所模擬的客觀世界中對象行為的多樣性,為軟件設(shè)計中的功能實現(xiàn)和任務(wù)調(diào)度提供了靈活性。多態(tài)性是指當(dāng)不同的對象收到相同的消息時產(chǎn)生不同的動作。即調(diào)用同一個函數(shù)名,可以根據(jù)需要,實現(xiàn)不同的功能。C++支持兩種多態(tài)性:編譯時多態(tài)性和運行時多態(tài)性。本章所敘述的內(nèi)容是運行時多態(tài)性的工作機制和實現(xiàn)方法。第二頁,共八十頁,編輯于2023年,星期五編譯時多態(tài)性:又稱靜態(tài)多態(tài),是指在編譯階段根據(jù)函數(shù)名和參數(shù)確定應(yīng)該調(diào)用哪一個函數(shù),其過程稱為靜態(tài)關(guān)聯(lián)或早期關(guān)聯(lián)。運行時多態(tài)性:又稱為動態(tài)多態(tài)性,是指在編譯階段根據(jù)函數(shù)名和參數(shù)無法確定應(yīng)該調(diào)用哪一個函數(shù),必須在程序的執(zhí)行過程中,根據(jù)具體的執(zhí)行情況來動態(tài)地確定。這種關(guān)聯(lián)稱為動態(tài)關(guān)聯(lián)或后期關(guān)聯(lián)。實現(xiàn)的途徑是:繼承和虛函數(shù)。只使用編譯時多態(tài)性機制的程序設(shè)計只能稱為基于對象(ObjectBased)的程序設(shè)計,使用了運行時多態(tài)性機制,才能稱為是面向?qū)ο螅∣bjectOriented)的程序設(shè)計。第三頁,共八十頁,編輯于2023年,星期五所謂運行多態(tài)性是相對與編譯多態(tài)性而言的。二者的共同作用是實現(xiàn)對象行為的多樣性,但在實現(xiàn)方法上和對程序運行控制的作用上有著很大的不同。1編譯多態(tài)性的局限性由于編譯多態(tài)性是通過函數(shù)重載實現(xiàn)的,因此對象行為的確定(函數(shù)的運行綁定)只能由編譯器根據(jù)程序源代碼的安排在編譯過程中實現(xiàn)。這樣實現(xiàn)的對象行為多樣性雖然具有良好的可讀性,但在控制程序運行和對象行為多樣性方面存在著局限性:6.1運行多態(tài)性第四頁,共八十頁,編輯于2023年,星期五⑴對象行為的多樣性必須是預(yù)先確定可知的,程序運行的控制必須是預(yù)先規(guī)劃固定的,因此只能解決軟件所模擬的客觀世界中那些可以預(yù)先確定和控制的事件的實現(xiàn)。⑵調(diào)用重載函數(shù)的不同版本(確定對象行為的多樣性)是依據(jù)參數(shù)(個數(shù)和類型)的差別實現(xiàn)的(派生類對基類同名、同參數(shù)成員函數(shù)的覆蓋除外),即發(fā)送給不同對象的消息并不完全一致。從這一點分析,編譯多態(tài)性所實現(xiàn)的并非完備意義上的多態(tài)性,即不同對象接收到同一函數(shù)調(diào)用(函數(shù)名和參數(shù)完全一致),表現(xiàn)出不同的行為。第五頁,共八十頁,編輯于2023年,星期五2運行多態(tài)性要實現(xiàn)的目標運行多態(tài)性要實現(xiàn)的目標是在保持編譯多態(tài)性所能實現(xiàn)的功能的基礎(chǔ)上,克服編譯多態(tài)性的局限性:⑴能根據(jù)程序運行中狀態(tài)的變化,動態(tài)確定接收函數(shù)調(diào)用的對象和對象行為的多樣性,使得軟件能夠?qū)崿F(xiàn)所模擬的客觀世界中那些無法預(yù)先確定事件的發(fā)生和完成。⑵完全相同的函數(shù)調(diào)用(函數(shù)名和參數(shù)完全一致)能夠觸發(fā)(調(diào)用)不同類對象的特定行為,從而實現(xiàn)完備意義的多態(tài)性,使得復(fù)雜的對象行為控制變得更加簡單、統(tǒng)一。第六頁,共八十頁,編輯于2023年,星期五3運行多態(tài)性的實現(xiàn)機制使程序具有運行多態(tài)性的關(guān)鍵是能否在程序運行期間,根據(jù)程序的運行狀態(tài),動態(tài)地修改程序的運行控制指針。C++(包括C)的編程要素——指針變量為動態(tài)多態(tài)性提供了基本實現(xiàn)機制,即程序能夠在運行期間通過修改指針變量的當(dāng)前值,動態(tài)地確定程序的當(dāng)前操作和操作所施加的數(shù)據(jù)(對象)。指針變量分為兩種:第七頁,共八十頁,編輯于2023年,星期五⑴數(shù)據(jù)指針變量用于動態(tài)確定操作數(shù)據(jù),即通過指針而不是使用數(shù)據(jù)變量名訪問數(shù)據(jù)變量。其定義格式為:數(shù)據(jù)類型*指針變量名;例如:double*pt;數(shù)據(jù)指針變量的值是數(shù)據(jù)空間的首地址,賦值合法性依據(jù)是首地址所指示數(shù)據(jù)的類型。例如:doubled;inti;pt=&d;//合法pt=&i;//非法第八頁,共八十頁,編輯于2023年,星期五⑵函數(shù)指針變量用于動態(tài)確定操作,即通過指針而不是使用函數(shù)名實現(xiàn)函數(shù)調(diào)用。其定義格式為:返回數(shù)據(jù)類型(*指針變量名)(參數(shù)據(jù)類型,…);例如:int(*fun)(int,int);函數(shù)指針變量的值是函數(shù)的調(diào)用地址,賦值合法性依據(jù)是調(diào)用地址所指示的函數(shù)類型(函數(shù)的返回類型,參數(shù)個數(shù)、類型和順序)。例如:intmax(int,int);voidcount(double);fun=max;//合法或fun=&max;fun=count;//非法第九頁,共八十頁,編輯于2023年,星期五在面向?qū)ο蟪绦蛟O(shè)計中操作和操作所施加的數(shù)據(jù)是綁定和封裝在對象中的。因此,實現(xiàn)動態(tài)多態(tài)性是在程序運行期間,根據(jù)運行狀態(tài),動態(tài)改變接收同一函數(shù)調(diào)用的對象,由對象的行為確定操作和操作所施加的數(shù)據(jù),從而實現(xiàn)動態(tài)地修改程序的運行控制指針。響應(yīng)同一函數(shù)要求被調(diào)用的對象成員函數(shù)原型(函數(shù)名,返回類型,參數(shù)個數(shù)、類型和順序)一致,顯然這是函數(shù)重載所不能支持的。函數(shù)指針雖然能夠支持動態(tài)多態(tài)性,但在面向?qū)ο蟪绦蛟O(shè)計中直接使用函數(shù)指針既煩瑣又違背面向?qū)ο蟮脑O(shè)計原則。因此必須引入新的編程機制——虛函數(shù)。6.2虛函數(shù)第十頁,共八十頁,編輯于2023年,星期五通過指針變量動態(tài)確定接收同一函數(shù)的對象是實現(xiàn)動態(tài)多態(tài)性的第一步,首要條件。1一般對象的指針⑴一般對象的指針與系統(tǒng)預(yù)定義數(shù)據(jù)的指針,在定義和使用語法上都相同;⑵無派生關(guān)系的不同類對象指針之間不允許相互轉(zhuǎn)換,如果強制轉(zhuǎn)換,可能會引起不可預(yù)期的結(jié)果。例如:6.2.1對象指針第十一頁,共八十頁,編輯于2023年,星期五#include<iostream>classclass1{inta,b;public:class1(intx,inty){a=x;b=y;}voidshow(){cout<<"my_base-----------\n";cout<<a<<""<<b<<endl;}};第十二頁,共八十頁,編輯于2023年,星期五classclass2{intc;public:class2(intx){c=x;}voidshow(){cout<<"my_class----------\n";cout<<"c="<<c<<endl;}};第十三頁,共八十頁,編輯于2023年,星期五voidmain(){class1c1(50,50),*ptr;class2c2(30);ptr=&c1;ptr->show();ptr=&c2;//錯誤ptr->show();}如果將代碼ptr=&c2;修改為ptr=(class1*)&c2;則會引起不可預(yù)測的執(zhí)行結(jié)果,例如:my_base-----------5050my_base-----------306684136第十四頁,共八十頁,編輯于2023年,星期五2具有派生層次的類對象指針由于派生類是由基類繼承定義的,即派生類的對象中總是包含著基類對象部分,并在派生類對象的創(chuàng)建過程中首先被創(chuàng)建。因此,基類對象和派生類對象的首地址所指示的對象類型是向后兼容的,即允許基類指針既可以指向基類對象,也可以指向派生類對象。第十五頁,共八十頁,編輯于2023年,星期五使用基類指針時要注意以下幾個問題:⑴基類指針可以指向該基類的公有派生類對象,但不能自動指向該基類的私有派生類對象(除非使用強制轉(zhuǎn)換實現(xiàn))。例如:classbase{...};classderive:base{...};voidmain(voidmain(){baseobj1,*ptr;deriveobj2;ptr=&obj1;ptr=&obj2;//不能自動指向其私有派生類對象,但可

//強制轉(zhuǎn)換,例如ptr=(base*)&obj2。}第十六頁,共八十頁,編輯于2023年,星期五子類型

如果一個特定的類型S,當(dāng)且僅當(dāng)它提供了類型T的行為時,則稱類型S是類型T的子類型。子類型體現(xiàn)了類型間的一般與特殊的關(guān)系。在C++中,子類型的概念是通過公有繼承(或公有派生)來實現(xiàn)的。根據(jù)繼承方式的概念,我們知道,按公有繼承的方式產(chǎn)生的派生類中,必然包含了原來基類中的全部成員。因此,一個公有派生類的對象可以提供其基類對象的全部行為(基類的全部接口),也就是說,在程序中可以把一個公有派生類對象當(dāng)作其基類對象來處理。TS公有繼承第十七頁,共八十頁,編輯于2023年,星期五例:子類型的概念及實現(xiàn)示例。#include<iostream.h>classA//定義類A{private:inta;public:A(inti=0){a=i;}voidprint();};voidA::print(){cout<<"InclassA,print()iscalled."<<endl;}第十八頁,共八十頁,編輯于2023年,星期五classB:publicA//定義類B,類B是類A的公有派生類{private:intb;public:B(intj=-1){b=j;}};//B是A的子類型voidcommfun(A&aref){aref.print();}第十九頁,共八十頁,編輯于2023年,星期五voidmain(){Aa;commfun(a);//以基類A的對象a作為實參調(diào)用函數(shù)commfunBb;commfun(b);//以派生類B的對象b調(diào)用函數(shù)commfun

//即將公有派生類對象b當(dāng)作其基類A的對象處理}程序運行結(jié)果:InclassA,print()iscalled.InclassA,print()iscalled.第二十頁,共八十頁,編輯于2023年,星期五說明:(1)在本例中,類B是類A的公有派生類,函數(shù)commfun()的形參是一個基類A對象的引用,所以在main函數(shù)中,把基類A的對象a作為實參調(diào)用函數(shù)commfun()時,產(chǎn)生的結(jié)果是不言而喻的。但在main函數(shù)中,當(dāng)把類B的對象b作為實參調(diào)用函數(shù)commfun()時,函數(shù)commfun()仍能正常工作,且打印結(jié)果與對象a作為實參時的結(jié)果相同,這說明,在程序中可以把一個公有派生類對象當(dāng)作其基類對象來處理。(2)將類型B的對象b傳遞給函數(shù)commfun()處理是在程序運行時發(fā)生的。但在程序編譯時,編譯器只能對源程序代碼進行靜態(tài)檢查。P189例5.10第二十一頁,共八十頁,編輯于2023年,星期五⑵不允許派生類指針指向該派生類的基類對象。例如:classbase{...};classderive:publicbase{...};voidmain(){baseobj1;deriveobj2,*ptr;ptr=&obj2;ptr=&obj1;//派生類指針不能指向它的基類對象}第二十二頁,共八十頁,編輯于2023年,星期五⑶使用基類指針訪問公有派生類對象時,只能自動訪問派生類對象中的基類對象部分。例如:classbase{…public:voidshow();};classderive:publicbase{...public:voidshow();};第二十三頁,共八十頁,編輯于2023年,星期五基類對象派生類對象Baseb;Derived;Base*basep;basepbasep=&b;basepbasep=&d;basep只能引用從基類繼承來的成員。xShow()xShow()yShow()basep->Show();basep->Show()基類指針派生類對象基類對象第二十四頁,共八十頁,編輯于2023年,星期五classPoint{ floatx,y;public: Point(){} Point(floati,floatj){ x=i; y=j; }

floatarea(void) { return0.0; }};constfloatPi=3.14159;classCircle:publicPoint{ //類Point的派生類 floatradius;public: Circle(floatr){ radius=r; }

floatarea(void) {returnPi*radius*radius; }};voidmain(void){Point*pp; //基類指針,可以將派生類對象的地址賦給基類指針Circlec(5.4321);pp=&c;cout<<pp->area()<<endl; //調(diào)用的是基類中有的公有函數(shù)}在基類和派生類中具有相同的公有函數(shù)area()。在這種情況下,使用基類的指針時,只能訪問從相應(yīng)基類中繼承來的成員,而不允許訪問在派生類中增加的成員。輸出為0第二十五頁,共八十頁,編輯于2023年,星期五通過前面的分析不難看出,面向?qū)ο蟮睦^承特性已經(jīng)為實現(xiàn)動態(tài)多態(tài)性提供了兩個基本條件:⑴在具有派生層次結(jié)構(gòu)的各個類中定義同原型(函數(shù)名,返回類型和參數(shù)個數(shù)、類型、順序均一致)而操作各異的成員函數(shù)為不同類對象響應(yīng)同一函數(shù)調(diào)用實現(xiàn)行為多樣性提供了條件。⑵使用基類指針既可以訪問基類對象又可以訪問派生類對象為動態(tài)確定接收同一函數(shù)調(diào)用的不同類對象提供了條件。6.2.2為什么要引入虛函數(shù)第二十六頁,共八十頁,編輯于2023年,星期五但僅有上述兩個條件仍然無法實現(xiàn)動態(tài)多態(tài)性,因為要通過基類指針調(diào)用派生類的成員函數(shù),必須將基類指針強制轉(zhuǎn)換為派生類指針。實現(xiàn)這樣的強制轉(zhuǎn)換必須在編譯時進行靜態(tài)綁定,而不能實現(xiàn)運行時的動態(tài)綁定。因此,必須引入一種新的編程機制,使得對派生層次結(jié)構(gòu)中各類的同原型成員函數(shù)的調(diào)用既能夠通過函數(shù)名編譯綁定,又能夠通過函數(shù)的調(diào)用地址運行綁定。顯然,實現(xiàn)運行綁定的最好途徑是使用函數(shù)指針。這種新的編程機制就是虛函數(shù)。第二十七頁,共八十頁,編輯于2023年,星期五1虛函數(shù)的定義⑴虛函數(shù)是用來定義基類和公有派生類的同名且同類型成員函數(shù)之間的多態(tài)關(guān)聯(lián)關(guān)系的實現(xiàn)機制;⑵虛函數(shù)必須在基類中首先定義,方法是在要定義為虛函數(shù)的成員函數(shù)聲明中冠以關(guān)鍵字virtual;⑶虛函數(shù)一旦被定義,可以在一個或多個直接或間接的公有派生類中重新定義;⑷虛函數(shù)重新定義時,其函數(shù)原型,即包括類型、函數(shù)名、參數(shù)(類型、個數(shù)和順序),都必須與基類中的原型完全一致。例如:6.2.3虛函數(shù)的定義及使用第二十八頁,共八十頁,編輯于2023年,星期五classbase{…public:voidshow();};classderive:publicbase{...public:voidshow();};第二十九頁,共八十頁,編輯于2023年,星期五基類對象派生類對象Baseb;Derived;basepbasepxShow()xShow()yShow()basep->Show()Base*basep;basep=&b;basep=&d;basep->Show();即指向派生類新增的成員函數(shù)需要將基類中的Show()說明為虛函數(shù)第三十頁,共八十頁,編輯于2023年,星期五classPoint{floatx,y;public: Point(){} Point(floati,floatj){ x=i; y=j; }

virtual

floatarea(void) {return0.0;}};constfloatPi=3.14159;classCircle:publicPoint{ //類Point的派生類 floatradius;public: Circle(floatr){ radius=r; }

floatarea(void) {returnPi*radius*radius;}};voidmain(void){Point*pp; //基類指針,可以將派生類對象的地址賦給基類指針Circlec(5.4321);pp=&c;cout<<pp->area()<<endl;//調(diào)用虛函數(shù)}將area()聲明為虛函數(shù),編譯器對其進行動態(tài)聚束,按照實際對象c調(diào)用了Circle中的函數(shù)area()。使Point類中的area()與Circle類中的area()有一個統(tǒng)一的接口。輸出:92.7011聲明為虛函數(shù)調(diào)用虛函數(shù)虛函數(shù)再定義第三十一頁,共八十頁,編輯于2023年,星期五#include<iostream.h>classbase{…public:virtualvoidwho(){cout<<"base\n";}//定義虛函數(shù)virtualvoidshow(){cout<<“propertiesofbase:…\n";}//定義虛函數(shù)};classfirst:publicbase{…public:voidwho(){cout<<"thefirstderivation\n";}//重新定義虛函數(shù)};第三十二頁,共八十頁,編輯于2023年,星期五classsecond:publicfirst{…public:voidwho(){cout<<"thesecondderivation\n";}//重新定義虛函數(shù)};第三十三頁,共八十頁,編輯于2023年,星期五intmain(){baseobj1,*ptr;firstobj2;secondobj3;ptr=&obj1;ptr->who();//調(diào)用base類的who()版本ptr->show();//調(diào)用base類的show()版本ptr=&obj2;ptr->who();//調(diào)用first類的who()版本ptr->show();//調(diào)用base類的show()版本第三十四頁,共八十頁,編輯于2023年,星期五ptr=&obj3;ptr->who();//調(diào)用second類的who()版本ptr->show();//調(diào)用base類的show()版本return0;}執(zhí)行結(jié)果:basepropertiesofbase:…thefirstderivationpropertiesofbase:…thesecondderivationpropertiesofbase:…第三十五頁,共八十頁,編輯于2023年,星期五得到上述理想的動態(tài)多態(tài)性結(jié)果的原因是:⑴定義了虛函數(shù)的基類增加了一個存放虛函數(shù)調(diào)用地址函數(shù)指針表virtualtable,該表的大小取決于虛函數(shù)的個數(shù),該表的首地址由基類的內(nèi)部指針vptr指示。⑵在派生層次中的各個派生類會從基類中直接或間接繼承虛函數(shù)指針表virtualtable和指針vptr。如果虛函數(shù)在派生類被重新定義,則virtualtable中的調(diào)用地址將被重新定義虛函數(shù)的調(diào)用地址替換,否則仍然使用直接基類虛函數(shù)的調(diào)用地址。第三十六頁,共八十頁,編輯于2023年,星期五⑶調(diào)用虛函數(shù)的運行綁定是由基類指針動態(tài)指向派生層次中某個類的對象,通過該對象的內(nèi)部指針vptr所指示的virtualtable中的相應(yīng)調(diào)用地址實現(xiàn)的。下圖示意了虛函數(shù)的定義和調(diào)用綁定關(guān)系:第三十七頁,共八十頁,編輯于2023年,星期五第三十八頁,共八十頁,編輯于2023年,星期五2虛函數(shù)與重載函數(shù)的區(qū)別⑴虛函數(shù)的首次定義和重新定義實際上是在類派生定義層次中的同名函數(shù)覆蓋定義。因此,虛函數(shù)的首次定義必須在基類中,重新定義只能在基類的不同派生類中,而不能發(fā)生在同一類定義中,也不能是全局函數(shù)。而函數(shù)重載既可以發(fā)生在同一類定義中,也可以是全局函數(shù)。第三十九頁,共八十頁,編輯于2023年,星期五⑵函數(shù)重載必須具有相同的函數(shù)名和不同的參數(shù)(個數(shù)、類型或順序)。而函數(shù)的返回類型不是區(qū)別重載函數(shù)不同版本的標志,但如果兩個函數(shù)只是返回類型不同,則會導(dǎo)致二義性錯誤。而虛函數(shù)重新定義必須保持與基類中的虛函數(shù)原型(返回類型、函數(shù)名、參數(shù)類型以及個數(shù)和順序)完全一致,否則會產(chǎn)生兩種情況:①如果僅返回類型不同,則會導(dǎo)致二義性錯誤。②如果僅函數(shù)名相同,而函數(shù)的參數(shù)有所不同,則系統(tǒng)會將該函數(shù)作為一般的重載函數(shù)處理,從而失去虛函數(shù)特性。第四十頁,共八十頁,編輯于2023年,星期五歸納虛函數(shù)的定義和使用應(yīng)注意下幾點:⑴在基類中,使用virtual可以將其public或protected部分的成員函數(shù)聲明為虛函數(shù)。⑵派生類對基類中聲明的虛函數(shù)進行重新定義時,關(guān)鍵字virtual可以忽略。但為了增強程序的可讀性,有時在派生類定義中,虛函數(shù)重定義時也使用關(guān)鍵字virtual。⑶虛函數(shù)被重新定義時,其函數(shù)原型與基類中的虛函數(shù)原型必須完全相同。第四十一頁,共八十頁,編輯于2023年,星期五⑷在一個派生類層次結(jié)構(gòu)中定義了虛函數(shù)后,就允許在執(zhí)行過程中,動態(tài)改變基類指針所指向的該基類派生的不同類對象,從而調(diào)用由虛函數(shù)確定的統(tǒng)一接口的不同操作版本。顯然,虛函數(shù)充分體現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計的動態(tài)多態(tài)性。⑸使用對象名和點運算符的方式也可以調(diào)用虛函數(shù)的不同版本,但是這種調(diào)用是在編譯時實現(xiàn)的靜態(tài)聯(lián)編,沒有充分利用虛函數(shù)的特性。因此,不需要通過基類指針實現(xiàn)運行時多態(tài)性調(diào)用的成員函數(shù)不應(yīng)該定義為虛函數(shù),避免不必要的內(nèi)存開銷。第四十二頁,共八十頁,編輯于2023年,星期五classPoint{floatx,y;public: Point(){} Point(floati,floatj){ x=i; y=j; }

virtual

floatarea(void) {return0.0;}//聲明為虛函數(shù)};constfloatPi=3.14159;classCircle:publicPoint{ //類Point的派生類 floatradius;public: Circle(floatr){ radius=r; }

floatarea(void) {returnPi*radius*radius;}//虛函數(shù)再定義};voidmain(void){Point*pp; //基類指針,可以將派生類對象的地址賦給基類指針Circlec(5.4321);cout<<c.area()<<endl;cout<<c.Point::area()<<endl; cout<<c.Circle::area()<<endl;}輸出:92.7011 0 92.7011可見,利用對象名進行調(diào)用與一般非虛函數(shù)沒有區(qū)別。用對象名調(diào)用area()第四十三頁,共八十頁,編輯于2023年,星期五⑹在派生類中,直接基類的虛函數(shù)指針表virtualtable總是被自動繼承。所以一個虛函數(shù)無論被公有繼承多少次,它仍然保持其虛函數(shù)的特性。因此,虛函數(shù)特別適合定義一族類的統(tǒng)一接口。⑺虛函數(shù)必須是其所在類的成員函數(shù),而不能是友元函數(shù),也不能是靜態(tài)成員函數(shù),因為虛函數(shù)調(diào)用要靠特定的對象來決定應(yīng)該動態(tài)綁定虛函數(shù)的哪個版本。但是虛函數(shù)可以被聲明為另一個類的友元成員。第四十四頁,共八十頁,編輯于2023年,星期五⑻構(gòu)造函數(shù)不允許是虛函數(shù),但析構(gòu)函數(shù)可以是虛函數(shù)。如果一個基類的析構(gòu)函數(shù)被說明為虛函數(shù),則它的派生類的析構(gòu)函數(shù)也是虛函數(shù)(不管它是否用關(guān)鍵字virtual進行了說明)。虛析構(gòu)函數(shù)的作用是在使用delete運算符刪除一個對象時,能確保析構(gòu)函數(shù)被正確地調(diào)用,即使用基類指針,以動態(tài)綁定應(yīng)該被調(diào)用析構(gòu)函數(shù),正確地釋放當(dāng)前基類指針所指向的類對象所占用的空間。例如:第四十五頁,共八十頁,編輯于2023年,星期五#include<iostream.h>classA{public:virtual~A(){cout<<"調(diào)用A::~A()"<<endl;}};classB:publicA{char*buf;public:B(inti){buf=newchar[i];}virtual~B(){delete[]buf;cout<<"調(diào)用B::~B()"<<endl;}};第四十六頁,共八十頁,編輯于2023年,星期五voidfun(A*a){deletea;}voidmain(){A*a=newB(10);fun(a);}運行結(jié)果:調(diào)用B::~B()調(diào)用A::~A()如果A的析構(gòu)函數(shù)不是虛函數(shù),則輸出結(jié)果如下:調(diào)用A::~A()顯然,調(diào)用A::~A()釋放B類對象是不正確的。第四十七頁,共八十頁,編輯于2023年,星期五例:用于求解三角形、正方形和圓面積的簡單程序。1問題分析:⑴三角形面積:底*高/2⑵正方形面積:邊長*邊長⑶圓面積:半徑*半徑*π歸納這些算法可以抽象出統(tǒng)一的表達式:x*y*C其中C為常量。對于不同的幾何圖形,其值不同:三角型:C=?正方形:C=1圓形:C=π不難看出,將面積求解設(shè)計為不同幾何圖形的統(tǒng)一接口有利于程序的結(jié)構(gòu)化和靈活性要求。6.2.4虛函數(shù)應(yīng)用實例第四十八頁,共八十頁,編輯于2023年,星期五為此,可以設(shè)計一個以概念圖形類figure為基類,三角形類triangle、正方形類square和圓類circles為figure的公有派生類的幾何圖形類族的層次結(jié)構(gòu)。該結(jié)構(gòu)中的各類具有統(tǒng)一的面積求解操作接口,即在figure中定義一個計算面積的虛函數(shù)show_area,其原型(統(tǒng)一接口描述)如下:virtualdoubleshow_area();并在各個派生類中重新定義該虛函數(shù)show_area的不同操作版本,使程序可以通過一個基類指針動態(tài)選擇不同的幾何圖形對象求解面積的目的。第四十九頁,共八十頁,編輯于2023年,星期五第五十頁,共八十頁,編輯于2023年,星期五例:能在一個鏈表中保存學(xué)生、教師和職員三種不同信息的程序。1.問題分析:⑴鏈表結(jié)構(gòu)要求組成鏈表的結(jié)點必須具有相同類型的值域,用于保存數(shù)據(jù)。⑵需要被保存的信息類型是不同的;如何在一個鏈表中保存不同類型的數(shù)據(jù),即設(shè)計一個“異質(zhì)鏈表”就成為實現(xiàn)問題需求的關(guān)鍵。改變鏈表結(jié)構(gòu)的基本性質(zhì)是不可能的,因此解決問題的方法是如何將不同類型數(shù)據(jù)轉(zhuǎn)換為同一類型族數(shù)據(jù),第五十一頁,共八十頁,編輯于2023年,星期五⑴歸納學(xué)生、教師和職員信息的共性,可以設(shè)計一個具有共同信息數(shù)據(jù)屬性的人員結(jié)點類person,并以person為基類公有派生學(xué)生結(jié)點類student、教師結(jié)點類teacher和職員結(jié)點類staff組成一個人員信息結(jié)點類族,并在基類person中定義將類對象插入鏈表和顯示信息的統(tǒng)一操作接口:virtualvoidinsert();virtualvoidprint();并在student、teacher和staff的定義中重新定義insert和print的不同操作版本第五十二頁,共八十頁,編輯于2023年,星期五⑵設(shè)計一個鏈表類list。其鏈域指針類型為person類型。使得鏈表的插入操作可以根據(jù)被插入的對象類型不同,動態(tài)綁定insert的不同操作版本,實現(xiàn)將不同類對象插入同一鏈表的目的。下面的類圖描述了各類的屬性、行為和類之間的相互關(guān)系:第五十三頁,共八十頁,編輯于2023年,星期五第五十四頁,共八十頁,編輯于2023年,星期五第五十五頁,共八十頁,編輯于2023年,星期五第五十六頁,共八十頁,編輯于2023年,星期五2詳細設(shè)計:⑴類設(shè)計:①基類personⅰ類定義:classperson{friendclasslist;//聲明鏈表類list為友元類protected:charname[20];//人員姓名intage;//人員年齡charadd[40];//人員住址chartele[15];//人員電話staticperson*ptr;//指向被插入對象的指針person*next;//鏈域指針第五十七頁,共八十頁,編輯于2023年,星期五public:person(char*name,intage,char*add,char*tele);virtualvoidprint();//顯示人員的共同信息virtualvoidinsert(){}//插入操作};ⅱ算法描述:print():使用標準輸出流cout順序按行輸出各類人員的共同信息(姓名name、年齡age、住址add和電話tele)數(shù)據(jù)和相應(yīng)的提示信息。insert():在本類不需要定義任何插入操作。第五十八頁,共八十頁,編輯于2023年,星期五②派生類studentⅰ類定義:classstudentpublicperson{friendclasslist;intlevel;floatgrade_point_averagefloataverage;public:student(char*name,intage,char*add,char*teleintlevel,floatgrade_point_average);voidprint();voidinsert();};第五十九頁,共八十頁,編輯于2023年,星期五ⅱ算法描述:

print():在調(diào)用person::print顯示人員共同信息的基礎(chǔ)上,增加學(xué)生類student的新增信息(年級level和平均成績grade_point_average)和信息提示的顯示行。

insert():在堆中動態(tài)復(fù)制student類對象,并使插入對象指針ptr指向堆中的student類對象。第六十頁,共八十頁,編輯于2023年,星期五③派生類teacherⅰ類定義:classteacherpublicperson{friendclasslist;floatsalary;public:teacher(char*name,intage,char*add,char*tele,floatsalary);voidprint();voidinsert();}第六十一頁,共八十頁,編輯于2023年,星期五ⅱ算法描述:

print():在調(diào)用person::print顯示人員共同信息的基礎(chǔ)上,增加教師類teacher的新增信息(薪金salary)和信息提示的顯示行。insert():在堆中動態(tài)復(fù)制teacher類對象,并使插入對象指針ptr指向堆中的tacher類對象。第六十二頁,共八十頁,編輯于2023年,星期五④派生類staffⅰ類定義:classstaff:publicperson{friendclasslist;floathourly_wages;public:teacher(char*name,intage,char*add,char*telefloathourly_wages);voidprint();voidinsert();};第六十三頁,共八十頁,編輯于2023年,星期五ⅱ算法描述:

print():在調(diào)用person::print顯示人員共同信息的基礎(chǔ)上,增加職員類staff的新增信息(計時工資hourly_wages)和信息提示的顯示行。insert():在堆中動態(tài)復(fù)制staff類對象,并使插入對象指針ptr指向堆中的staff類對象。第六十四頁,共八十頁,編輯于2023年,星期五⑤鏈表類listⅰ類定義:classlist{person*root;publicpublic:list();voidinsert_person(person*node);voidremove(char*name);voidprint_list();};第六十五頁,共八十頁,編輯于2023年,星期五第六十六頁,共八十頁,編輯于2023年,星期五第六十七頁,共八十頁,編輯于2023年,星期五第六十八頁,共八十頁,編輯于2023年,星期五第六十九頁,共八十頁,編輯于2023年,星期五6.3.1純虛函數(shù)和抽象類1純虛函數(shù)的概念從前面的實例中不難看出,有時定義基類只是描述一種概念,并不與任何具體的有意義的實例相聯(lián)系。因此這些基類的某些接口函數(shù)只須為它的公有派生類提供統(tǒng)一的接口規(guī)則,而無須也無法提供具體操作功能。例如,上例中person類的插入操作虛函數(shù)Insert。6.3抽象類第七十頁,共八十頁,編輯于2023年,星期五所以這類虛函數(shù)常常被定義為純虛函數(shù)。純虛函數(shù)的定義形式:virtualtype函數(shù)名(參數(shù)表)=0;例如:第七十一頁,共八十頁,編輯于2023年,星期五classshape{…public:virtualvoidshow()=0;//定義純虛函數(shù)virtualfloatarea()=0;//定義純虛函數(shù)…};classrectangles:publicshape{…public:voidshow();

溫馨提示

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

評論

0/150

提交評論