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

下載本文檔

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

文檔簡(jiǎn)介

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

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

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

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

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

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

virtual

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

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

virtual

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

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

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

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

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

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

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝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ù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論