C++面向?qū)ο蟪绦蛟O(shè)計(第4版)-課件 CH5_第1頁
C++面向?qū)ο蟪绦蛟O(shè)計(第4版)-課件 CH5_第2頁
C++面向?qū)ο蟪绦蛟O(shè)計(第4版)-課件 CH5_第3頁
C++面向?qū)ο蟪绦蛟O(shè)計(第4版)-課件 CH5_第4頁
C++面向?qū)ο蟪绦蛟O(shè)計(第4版)-課件 CH5_第5頁
已閱讀5頁,還剩88頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第5章多態(tài)本章主要教學(xué)內(nèi)容面向?qū)ο笳Z言的多態(tài)語言機(jī)制,多態(tài)的功能及其與軟件維護(hù)的關(guān)系靜態(tài)聯(lián)編、動態(tài)聯(lián)編及其與多態(tài)的關(guān)系虛函數(shù)、虛析構(gòu)函數(shù)、override和final純虛函數(shù)、抽象類和抽象類作接口的程序設(shè)計RTTI機(jī)制本章教學(xué)重點動態(tài)聯(lián)編及其與多態(tài)的關(guān)系虛函數(shù)的特點及其在多態(tài)程序設(shè)計中的應(yīng)用抽象類及其應(yīng)用虛析構(gòu)函數(shù)設(shè)計教學(xué)難點多態(tài)的技術(shù)原理和實現(xiàn)方法用抽象類作接口的技術(shù)原理、實現(xiàn)方法和程序設(shè)計用dynamic_cast實現(xiàn)基類和派生類類型轉(zhuǎn)換:向上轉(zhuǎn)換和向下轉(zhuǎn)換第5章多態(tài)1.多態(tài)的概念多態(tài)是面向?qū)ο蟪绦蛟O(shè)計語言的又一重要特征,指的是不同對象接收到同一消息時會產(chǎn)生不同的行為。簡單地說,多態(tài)就是在同一個類或繼承體系結(jié)構(gòu)的基類與派生類中,用同名函數(shù)來實現(xiàn)各種不同的功能。2.多態(tài)與繼承的關(guān)系繼承所處理的是類與類之間的層次關(guān)系問題而多態(tài)則是處理類的層次結(jié)構(gòu)之間,以及同一個類內(nèi)部同名函數(shù)的關(guān)系問題。但通常是指繼承結(jié)構(gòu)中基類和派生類之間通過同名虛函數(shù)實現(xiàn)不同函數(shù)功能的問題。5.1.1多態(tài)概述1、多態(tài)的實現(xiàn)形式對象根據(jù)所接收的消息而做出動作,同樣的消息為不同的對象接收時可導(dǎo)致完全不同的行動,該現(xiàn)象稱為多態(tài)性。解決的問題:單接口,多實現(xiàn)VoidF(家用電器*p){p->on();p->off();}Voidmain(){電視機(jī)a;電風(fēng)扇b;電冰箱c;F(&a)F(&b);……函數(shù)F以基類家用電器為接口,通過基指針實現(xiàn)了對派生類電視器的on和off函數(shù)的調(diào)用!事實上,它可以訪問任何派生對象的on和off函數(shù)2.多態(tài)的類型OOP中廣義的多態(tài)通常有3種表現(xiàn)形式重載多態(tài):包括函數(shù)重載和運算符重載;模板多態(tài):通過一個模板生成不同的函數(shù)或類(第7章介紹);繼承多態(tài):通過基類對象的指針(引用),調(diào)用不同派生類對象的重定義同名成員函數(shù),表現(xiàn)出不同的行為。一般情況下,多態(tài)即指這種類型。實際意義上的多態(tài)是指繼承多態(tài)!5.1.1多態(tài)的概念3.實現(xiàn)多態(tài)的條件要實現(xiàn)繼承多態(tài)性,須具備三個必要條件:要有繼承;派生類要覆蓋(重定義)基類的虛函數(shù),即派生類具有和基類函數(shù)據(jù)原形完全相同的虛成員函數(shù);把基類的指針或引用綁定到派生類對象上。也就是說,沒有繼承,或者沒有派生類沒有重定義基類的虛函數(shù),或者具備前兩者,但直接把派生類對象賦值給基類對象(沒有通過指針或引用),都不能實現(xiàn)多態(tài)。5.1.1多態(tài)的概念【例5-1】設(shè)計一個管理動物聲音的軟件。(1)問題分析所有的動物都會發(fā)聲,但是當(dāng)沒有說明是貓,狗或鳥等具體動物時,則不知道它發(fā)出什么聲音。雖然無法實施,但又確實知道動物有聲音,面向?qū)ο蟪绦蛟O(shè)計語言提出了用(純)虛函數(shù)來表達(dá)這類確實存在但又無法實施的抽象概念。當(dāng)?shù)搅丝芍木唧w動物時,它會發(fā)出什么聲音就是明確的了,此時再對相應(yīng)的虛函數(shù)進(jìn)行編碼實現(xiàn)。5.1.1多態(tài)的概念(2)數(shù)據(jù)抽象用Animal表示動物類,用虛成員函數(shù)sound表示動物會發(fā)聲這一行為。Dog,Cat,Wolf,Bird則是具體的動物,它們可以繼承Animal的所有特征和行為。每類動物能夠發(fā)出什么聲音是明確的,而且各不相同,需要覆蓋(重定義)從Animal繼承來的sound成員函數(shù)。Animal和Dog等動物的繼承關(guān)系形成了圖5-1所示的繼承層次結(jié)構(gòu)5.1.1多態(tài)的概念動物繼承體系動物有聲音,但具體是什么聲音卻不知道。因此,無法實現(xiàn)這個函數(shù),用虛函數(shù)表達(dá)這一概念!但是,具體動物類的聲音是明確的,它可以實現(xiàn)sound函數(shù)的代碼!甚至……,現(xiàn)有鳥類幾萬年后變異后的新興鳥類的聲音,通過基類Animal的指針也可以訪問!這一特征對程序功能擴(kuò)展帶來極大的方便!5.1.1多態(tài)的概念據(jù)Animal繼承體系,可以設(shè)計出下面的簡易類classAnimal{//不知道動物會怎么叫! public:

virtualvoidsound(){cout<<"unknow!"<<endl;}};classDog:publicAnimal{//狗兒叫聲“汪汪汪!” public: voidsound(){cout<<"wang,wang,wang!"<<endl;}};classCat:publicAnimal{//貓兒叫聲“”喵喵喵! public: voidsound(){cout<<"miao,miao,miao!"<<endl;}};classWlof:publicAnimal{//狼嚎叫聲“”! public: voidsound(){cout<<"wu,wu,wu!"<<endl;}};(3)Anmal的多態(tài)實現(xiàn)多態(tài)是指當(dāng)基類的指針(或引用)綁定到派生類對象上,通過此指針(引用)調(diào)用基類的成員函數(shù)時,實際上調(diào)用到的是該函數(shù)在派生類中的覆蓋函數(shù)版本。例如,對于上面的繼承結(jié)構(gòu),下面的pA指針實現(xiàn)的就是多態(tài)。voidmain(){ Animal*pA; Dogdog; Catcat; Wlofwlof; pA=&dog;pA->sound();//pA調(diào)用Dog的sound函數(shù) pA=&cat;pA->sound(); //pA調(diào)用Cat的sound函數(shù) pA=&wlof;pA->sound(); //pA調(diào)用Wlof的sound函數(shù)}5.1.1多態(tài)的概念Sound()pAdogcatwlofSound()Sound()更一般地,多態(tài)更多地體現(xiàn)在用基類對象的指針或引用作為函數(shù)的參數(shù),通過它調(diào)用派生類對象中的覆蓋函數(shù)版本。例如,針對Animal繼承體系,設(shè)計animalSound函數(shù)管理每種動物的聲音,多態(tài)能夠很好地實現(xiàn)此需求。voidanimalSound(Animal&animal){animal.sound();}animalSound函數(shù)體現(xiàn)了“一個接口,多種實現(xiàn)”。即以基類Animal的引用為接口,可以訪問到圖5-1所示繼承體系中Animal類的任何派生類對象的sound函數(shù)。Animal*pA;Dogdog;Catcat;Wlofwlof;animalSound(dog);//調(diào)用Dog::sound()animalSound(cat);//調(diào)用Cat::sound()animalSound(wlof);//調(diào)用Wlof::sound()5.1.1多態(tài)的概念5.1.2多態(tài)的意義多態(tài)使開發(fā)者在沒有確定某些具體功能如何實施的情況下,可以站在高層(基類)設(shè)計并完成系統(tǒng)開發(fā),等新功能明確并實現(xiàn)后,通過多態(tài)可以很容易地融入系統(tǒng)。多態(tài)對于軟件開發(fā)和維護(hù)而言,意義重大。1.可替換性多態(tài)對已存在代碼具有可替換性。軟件升級變得簡單易行。例如,在Animal繼承體系中,如果現(xiàn)有的Dog類需要更新,重新編寫了sound成員函數(shù),只要該函數(shù)的原形保持不變,然后用新編寫的Dog類更換以前的Dog類,原系統(tǒng)不受影響就能夠調(diào)用新類的功能。2.可擴(kuò)充性增加新的子類不影響已存在類的多態(tài)性、繼承性,以及其他特性的運行和操作。在不影響原系統(tǒng)功能的情況下,很容易派生新類,擴(kuò)展系統(tǒng)新功能。3.靈活性在多態(tài)程序結(jié)構(gòu)中,基類提供接口,派生類提供實現(xiàn),兩者可以分離開來,使軟件功能的整體設(shè)計和功能的逐步實現(xiàn)、擴(kuò)展更加靈活。5.1.3多態(tài)與聯(lián)編1、聯(lián)編的概念一個程序常常會調(diào)用到來自于不同文件或C++庫中的資源(如函數(shù)、對話框)等,需要經(jīng)過編譯、連接才能形成為可執(zhí)行文件,在這個過程中要把調(diào)用函數(shù)名與對應(yīng)函數(shù)(這些函數(shù)可能來源于不同的文件或庫)關(guān)聯(lián)在一起,這個過程就是綁定(binding),又稱聯(lián)編。

2、聯(lián)編的類型根據(jù)把調(diào)用函數(shù)名和調(diào)用函數(shù)綁定在一起的時間,分為靜態(tài)聯(lián)編和動態(tài)聯(lián)編。3、靜態(tài)聯(lián)編靜態(tài)聯(lián)編又稱靜態(tài)綁定,是指在編譯程序時就根據(jù)調(diào)用函數(shù)提供的信息,把它所對應(yīng)的具體函數(shù)確定下來,即在編譯時就把調(diào)用函數(shù)名與具體函數(shù)綁定在一起。4、動態(tài)聯(lián)編動態(tài)聯(lián)編又稱動態(tài)綁定,是指在編譯程序時還不能確定函數(shù)調(diào)用所對應(yīng)的具體函數(shù),只有在程序運行過程中才能夠確定函數(shù)調(diào)用所對應(yīng)的具體函數(shù),即在程序運行時才把調(diào)用函數(shù)名與具體函數(shù)綁定在一起。5.1.3多態(tài)與聯(lián)編4、多態(tài)性的實現(xiàn)方式編譯時多態(tài)性:靜態(tài)聯(lián)編(連接)系統(tǒng)在編譯時就決定如何實現(xiàn)某一動作,即對某一消息如何處理。靜態(tài)聯(lián)編具有執(zhí)行速度快的優(yōu)點。在C++中的編譯時多態(tài)性是通過函數(shù)重載和運算符重載實現(xiàn)的。運行時多態(tài)性:動態(tài)聯(lián)編(連接)系統(tǒng)在運行時動態(tài)實現(xiàn)某一動作,即對某一消息在運行過程實現(xiàn)其如何響應(yīng)。動態(tài)聯(lián)編為系統(tǒng)提供了靈活和高度問題抽象的優(yōu)點。在C++中的運行時多態(tài)性是通過繼承和虛函數(shù)實現(xiàn)的。5.1.3多態(tài)與聯(lián)編關(guān)于派生類和基類關(guān)系的說法正確的是()。派生類對象可以作為基類的引用基類指針可以指向派生類對象可以將基類對象賦值給基類對象可以將派生類對象賦值給基類對象ABCD提交多選題1分5.2虛函數(shù)

5.2.1虛函數(shù)的意義1、回顧:4.6基類與派生類的賦值相容(Page208)派生類對象可以賦值給基類對象。派生類對象的地址可以賦值給指向基類對象的指針。派生類對象可以作為基類對象的引用。賦值相容的問題:不論哪種賦值方式,都只能通過基類對象(或基類對象的指針或引用)訪問到派生類對象從基類中繼承到的成員,不能借此訪問派生類定義的成員。2、虛函義解決的問題虛函數(shù)使得通過基類對象的指針或引用訪問派生類重定義的虛成員函數(shù)可以施行。classB{public:

intf(){}}classD:publicB{intg(){}

intf(){}}Dd;Bb=d;B*pb=&d;B&rb=d;d.f();//B::f

()pb->f();//B::f()rb.f();//B::f()d.g();//errorpb->g();//errorrb.g();//error5.2.1虛函數(shù)的意義【例5-2】某公司有經(jīng)理、銷售員、小時工等多類人員。經(jīng)理按周計算薪金;銷售員每月底薪800元,然后加銷售提成,每銷售一件產(chǎn)品提取銷售利潤的5%;小時工按小時計算薪金。每類人員都有姓名和身份證號等數(shù)據(jù),設(shè)計管理員工薪金的程序。(1)問題分析經(jīng)理、銷售員、小時工等各類工作人員都是公司的雇員,每類人員都有姓名和身份證號等信息,可以將它們抽象為雇員類Employee,其余人員則從Employee類派生。(2)數(shù)據(jù)抽象雇員類Employee,用name和Id分別表示姓名和身份證編號;將經(jīng)理抽象成Manager類,用WeeklySalary表示周工資,并設(shè)計setSalary/getSalary修改和訪問周工資。將銷售員抽象成SalesPerson類,用basePay表示底薪,salesValue表示銷售額,以及setBasePay/getBasePay,setSalesValue/getSalesValue成員函數(shù)設(shè)置和讀取底薪與銷售額數(shù)據(jù)。將小時工抽象成HourPerson類,用hprice表示小時工資,用hour表示工作時間,并用setHprice/getHprice,setHour/getHour設(shè)置/讀取小時工價及工作時間。5.2.1虛函數(shù)的意義數(shù)據(jù)抽象結(jié)果——類繼承體系//Eg5-2.cpp#include<iostream>#include<string>usingnamespacestd;classEmployee{public:

Employee(stringName,stringid){name=Name;Id=id;}stringgetName(){returnname;} //返回姓名

stringgetID(){returnId;} //返回身份證號

floatgetSalary(){return0.0;} //返回薪水

voidprint(){ //輸出姓名和身份證號

cout<<"姓名:"<<name<<"\t\t編號:"<<Id<<endl;}private:stringname;stringId;};人員管理的非虛函數(shù)簡化實現(xiàn)版本classManager:publicEmployee{public:Manager(stringName,stringid,floats=0.0):Employee(Name,id){WeeklySalary=s;}voidsetSalary(floats){WeeklySalary=s;} //設(shè)置經(jīng)理的周薪

floatgetSalary(){returnWeeklySalary;} //獲取經(jīng)理的周薪

voidprint(){ //打印經(jīng)理姓名、身份證、周薪

cout<<"經(jīng)理:"<<getName()<<"\t\t編號:"<<getID()<<"\t\t周工資:"<<getSalary()<<endl;}private:floatWeeklySalary; //周薪};voidmain(){Employeee("黃春秀","NO0009"),*pM;Managerm("劉大海","NO0001",128);m.print();pM=&m;pM->print();

Employee&rM=m;rM.print();}人員管理的非虛函數(shù)簡化實現(xiàn)版本程序的運行結(jié)果如下:經(jīng)理:劉大海編號:NO0001周工資:128姓名:劉大海編號:NO0001姓名:劉大海編號:NO0001輸出的第2、3行表明,通過基類對象的指針和引用只訪問到了在基類中定義的print函數(shù)。原因:pM->print()\rM.print()采用靜態(tài)聯(lián)編而pM、rM的類型都是Employee所以只能訪問Employee類的成員5.2.1虛函數(shù)的意義將基類Employee的print指定為虛函數(shù),如下形式:classEmployee{……

virtualvoidprint(){cout<<"姓名:"<<name<<"\t\t編號:"<<Id<<endl;}};將得到下面的程序運行結(jié)果:經(jīng)理:劉大海編號:NO0001周工資:128經(jīng)理:劉大海編號:NO0001周工資:128經(jīng)理:劉大海編號:NO0001周工資:1285.2.1虛函數(shù)的意義基類指針或引用指向派生類對象時,虛函數(shù)與非虛函數(shù)的對象,圖左為非虛函數(shù),圖右為虛函數(shù)5.2.1虛函數(shù)的意義print()非虛函數(shù),采用靜態(tài)聯(lián)編,只能綁定到pM,rM的定義類型Employee的print()上print()為虛函數(shù),采用動態(tài)聯(lián)編,運行到調(diào)用語句時,才綁定到pM,rM實際內(nèi)存對象類型Manager的print()上1、什么是虛函數(shù)用virtual關(guān)鍵字修飾的成員函數(shù),Virtual關(guān)鍵字其實質(zhì)是告知編譯系統(tǒng),被指定為virtual的函數(shù)采用動態(tài)聯(lián)編的形式編譯。只有類的成員函數(shù)才能聲明為虛函數(shù),普通函數(shù)(不屬于任何類)不能定義為虛函數(shù)多態(tài)類:擁有虛函數(shù)的類也稱為多態(tài)類。2、虛函數(shù)的定義形式classx{……virtualf(參數(shù)表);}3、虛函數(shù)的執(zhí)行機(jī)制如果基類中的非靜態(tài)成員函數(shù)被定義為虛函數(shù),且派生類覆蓋了基類的虛函數(shù),當(dāng)通過基類的指針或引用調(diào)用派生類對象中的虛函數(shù)時,編譯器將執(zhí)行動態(tài)綁定,調(diào)用到該指針(或引用)實際所指對象所在類中的虛函數(shù)版本。5.2.1虛函數(shù)的意義

4、虛函數(shù)的虛特征基類指針指向派生類的對象時,通過該指針(或引用)訪問其虛函數(shù)時將調(diào)用派生類的版本。例題:沒有虛函數(shù)的情況classB{public:voidf(){cout<<"B::f";};};classD:publicB{public:voidf(){cout<<"D::f";}};voidmain(){ Dd; B*pb=&d; pb->f();}B::f5.2.1虛函數(shù)的意義運行結(jié)果例題:虛函數(shù)版classB{public:virtualvoidf(){cout<<"B::f";};};classD:publicB{public:voidf(){cout<<"D::f";};};voidmain(){ Dd; B*pb=&d; pb->f();}總結(jié):通過指向派生類對象的基類指針訪問函數(shù)成員時,非虛函數(shù)由定義指針的類型決定調(diào)用的函數(shù)版本虛函數(shù)由指針實際指向的對象的類型決定調(diào)用的函數(shù)版本D::f5.2.1虛函數(shù)的意義運行結(jié)果5.2.2override和final11C++1.override解決的問題

classB{public: virtualvoidoutData(int

a){cout<<a;};};classD:publicB{public: voidoutData(doubleb){cout<<b;}};用override限定類D的outData函數(shù),編譯器就知道它是基類虛函數(shù)的覆蓋函數(shù)版本。當(dāng)該函數(shù)的參數(shù)表與基類的不同時,編譯器就會檢測到該錯誤,不允許通過編譯。classD:publicB{public: voidoutData(doubleb)override{cout<<b;}//錯誤};派生類D本意重定義從基類繼承到的outData虛函數(shù),但形參的類型不同。就不是重定義,編譯器會為D生成兩不同的重載版本的outData函數(shù),沒有實現(xiàn)虛函數(shù)的目的。2、override的注意事項override只能用在派生類中限定從基類繼承到的虛函數(shù)。如果用它限定基類的非虛函數(shù),或者派生類新定義的函數(shù),則會產(chǎn)生編譯錯誤。如,classB{public:

virtualvoidg1(inta){cout<<a;};voidg2(intb){cout<<b;}};classD:publicB{public:voidg1(intb)override

{cout<<b;}//正確

voidg2(intb)override{cout<<b;}//錯誤,基類B的g2不是虛函數(shù)voidg3(inbb)override{cout<<b;}//錯誤,基類B沒有g(shù)3函數(shù)voidg1(charb)override{cout<<b;}//錯誤,與基類的虛函數(shù)g1不匹配};5.2.2override和final11C++3、finalfinal用于限定只想讓派生類繼承,而不允許被覆蓋的虛成員函數(shù)。final只能限定虛函數(shù),被限定為final的成員函數(shù),則任何派生類對該函數(shù)的覆蓋定義都是錯誤的。5.2.2override和final11C++classB{public:

virtualvoidg1(inta){};voidg2(intb){}};classD:publicB{public:voidg1(intb)override

{}};classD1:publicD{public:voidg1(intx)final{cout<<x;}//正確,voidf(inty)final{cout<<y;}//錯誤,f

不是虛函數(shù)};classD2:publicD1{ voidg1(inta)override{}//錯誤,

D1已聲明g1為final};5.2.3虛函數(shù)的特性

1、一旦將某個成員函數(shù)聲明為虛函數(shù)后,它在繼承體系中就永遠(yuǎn)為虛函數(shù)了?!纠?-3】虛函數(shù)與派生類的關(guān)系。#include<iostream>#include<string>usingnamespacestd;classA{public:voidf(inti){cout<<“…A”<<endl;};//非虛函數(shù)};classB:publicA{public:

virtualvoidf(inti){cout<<"…B"<<endl;}//虛函數(shù)};classC:publicB{public:voidf(inti){cout<<“…C”<<endl;}//虛函數(shù)};classD:publicC{public:voidf(int){cout<<"…D"<<endl;}};voidmain(){A*pA,a;B*pB,b;Cc;Dd;pA=&a;pA->f(1); //A::f() //調(diào)用A::fpA=&b;pA->f(1); //A::f() //調(diào)用A::fpA=&c;pA->f(1); //A::f() //調(diào)用A::fpA=&d;pA->f(1); //A::f() //調(diào)用A::f}5.2.3虛函數(shù)的特性

2、如果基類定義了虛函數(shù),當(dāng)通過基類指針或引用調(diào)用派生類對象時,將訪問到它們實際所指對象中的虛函數(shù)版本。例如,若把例5-3中的main的pA指針修改為pB,將會體現(xiàn)虛函數(shù)的特征。voidmain(){A*pA,a;B*pB,b;Cc;Dd;//pB=&a;pB->f(1); //錯誤,派生類不能訪問基類對象

pB=&b;pB->f(1); //調(diào)用B::fpB=&c;pB->f(1); //調(diào)用C::fpB=&d;pB->f(1); //調(diào)用D::f}5.2.3虛函數(shù)的特性

5.2.2虛函數(shù)的特性

3、只有通過基類對象的指針和引用訪問派生類對象的虛函數(shù)時,才能體現(xiàn)虛函數(shù)的特性?!纠?-4】只能通過基類對象的指針和引用才能實現(xiàn)虛函數(shù)的特性。//Eg5-4.cpp#include<iostream>usingnamespacestd;classB{public:virtualvoidf(){cout<<"B::f"<<endl;};};classD:publicB{public:voidf(){cout<<"D::f"<<endl;};};voidmain(){Dd;B*pB=&d,&rB=d;Bb;b=d;b.f();pB->f();rB.f();}本程序的運行結(jié)果如下:第1行輸出沒有體現(xiàn)虛函數(shù)特征B::fD::fD::f5.2.3虛函數(shù)的特性

DBd&dpBrBBb4、派生類中的虛函數(shù)要保持其虛特征,必須與基類虛函數(shù)的函數(shù)原型完全相同,否則就是普通的重載函數(shù),與基類的虛函數(shù)無關(guān)?!纠?-5】基類B和派生類D都具有成員函數(shù)f

,但它們的參數(shù)類型不同,因此不能體現(xiàn)函數(shù)f在派生類D中的虛函數(shù)特性。//Eg5-5.cpp#include<iostream>usingnamespacestd;classB{public:virtualvoidf(inti){cout<<"B::f"<<endl;};};5.2.3虛函數(shù)的特性

classD:publicB{public:intf(charc){cout<<"D::f..."<<c<<endl;}};voidmain(){Dd;B*pB=&d,&rB=d,b;pB->f('1');rB.f('1');}本程序的運行結(jié)果如下:B::fB::f此運行結(jié)果表明,沒有實現(xiàn)虛特征!5.2.3虛函數(shù)的特性

5、派生類通過從基類繼承的成員函數(shù)調(diào)用虛函數(shù)時,將訪問到派生類中的版本?!纠?-6】派生類D的對象通過基類B的普通函數(shù)f調(diào)用派生類D中的虛函數(shù)g//Eg5-6.cpp#include<iostream>usingnamespacestd;classB{public:

voidf(){g();}virtualvoidg(){cout<<"B::g";}};classD:publicB{public:

voidg(){cout<<"D::g";}};voidmain(){Dd;

d.f();}5.2.3虛函數(shù)的特性

調(diào)用成員函數(shù)時,若為虛函數(shù),將訪問:實際調(diào)用對象對應(yīng)類中的函數(shù)版本!【例5-7】分析下面程序的輸出結(jié)果,理解虛函數(shù)的調(diào)用過程。classB{public:voidf(){cout<<"bf";}virtualvoidvf(){cout<<"bvf";}voidff(){vf();f();};virtualvoidvff(){vf();f();} };classD:publicB{public:voidf(){cout<<"df";}voidff(){f();vf();}voidvf(){cout<<"dvf";} };voidmain(){ Dd; B*pB=&d; pB->f(); pB->ff(); pB->vf(); pB->vff();}bfdvfbfdvfdvfbf5.2.3虛函數(shù)的特性

運行結(jié)果6、只有類的非靜態(tài)成員函數(shù)才能被定義為虛函數(shù),類的構(gòu)造函數(shù)和靜態(tài)成員函數(shù)不能定義為虛函數(shù)。原因是虛函數(shù)在繼承層次結(jié)構(gòu)中才能夠發(fā)生作用,而靜態(tài)成員是不能夠被繼承的,構(gòu)造函數(shù)雖然可被繼承,但它只用于創(chuàng)建本類對象。7、內(nèi)聯(lián)函數(shù)也不能是虛函數(shù)。因為內(nèi)聯(lián)函數(shù)采用的是靜態(tài)聯(lián)編的方式,而虛函數(shù)是在程序運行時才與具體函數(shù)動態(tài)綁定的,采用的是動態(tài)聯(lián)編的方式,即使虛函數(shù)在類體內(nèi)被定義,C++編譯器也將它視為非內(nèi)聯(lián)函數(shù)。5.2.3虛函數(shù)的特性

5.3虛析構(gòu)函數(shù)

為什么要用虛析構(gòu)函數(shù)?原因是:假定使用delete來銷毀一個指向派生類的基類指針,如果基類析構(gòu)函數(shù)不是虛函數(shù),就如一個普通成員函數(shù)那樣,delete函數(shù)調(diào)用的就是基類析構(gòu)函數(shù),而不會調(diào)用派生類的析構(gòu)函數(shù)。這樣,在通過基類對象的引用或指針調(diào)用派生類對象時,將致使對象析構(gòu)不徹底!【例5-8】在非虛析構(gòu)函數(shù)的情況下,通過基類指針對派生對象的析構(gòu)是不徹底的。//Eg5-8.cpp#include<iostream>usingnamespacestd;classA{public:

~A(){cout<<"callA::~A()"<<endl;}};classB:publicA{char*buf;public:B(inti){buf=newchar[i];}~B(){delete[]buf;cout<<"callB::~()"<<endl;}};voidmain(){A*a=newB(10);deletea;}程序運行結(jié)果:callA::~A()此結(jié)果表明沒有析構(gòu)buf5.3虛析構(gòu)函數(shù)

classA{public:

virtual

~A(){ cout<<"callA::~A()"<<endl; }};classB:publicA{ char*buf;public: B(inti){buf=newchar[i];}

virtual

~B(){ delete[]buf; cout<<"callB::~()"<<endl; }};voidmain(){ A*a=newB(10); deletea;}程序運行結(jié)果:callA::~A()callB::~()此結(jié)果表明回收了buf空間!例5-8的虛析構(gòu)函數(shù)版本1、虛函數(shù)的實現(xiàn)方式早期綁定與后期綁定early-binding:編譯時間完成的綁定late-binding:運行時間完成的綁定虛函數(shù)是通過后期綁定實現(xiàn)的編譯時間行為和運行時間行為編譯時間行為:通過靜態(tài)分析就可以確定的行為運行時間行為:通過動態(tài)分析(運行過程的分析)虛函數(shù)是通過動態(tài)行為分析實現(xiàn)的運行時間行為補充內(nèi)容:虛函數(shù)的實現(xiàn)技術(shù)

2、C++實現(xiàn)虛函數(shù)的技術(shù)C++通過虛函數(shù)表來實現(xiàn)虛函數(shù)的動態(tài)綁定。在編譯帶有虛函數(shù)的類時,C++將為該類建立一個虛函數(shù)表(vtable),在虛函數(shù)表中存放指向本類虛函數(shù)的指針,這些指針指向本類的虛函數(shù)地址。在有虛函數(shù)的類對象中,C++除了為它保存每個數(shù)據(jù)成員外,還保存了一個指向本類虛函數(shù)表的指針(vptr)。補充內(nèi)容:虛函數(shù)的實現(xiàn)技術(shù)

虛表vtable與虛表指針vptrclassX{inti,j,k;virtualf(){};virtualg(){};};…Xa,b,c;…補充內(nèi)容:虛函數(shù)的實現(xiàn)技術(shù)

classB{inti,j,k;virtualf(){};virtualg(){};};classD:publicB{inti,m;f(){};};…Dd;Bb;*pb=&d;pb->f();pb->g();…補充內(nèi)容:虛函數(shù)的實現(xiàn)技術(shù)

1、純虛函數(shù)與抽象類的概念在有些情況下,定義類的時候卻并不知道如何實現(xiàn)它的某些成員函數(shù),但這些函數(shù)又確實存在。定義該類的目的也并不是為了建立它的對象,而是為了表達(dá)某種概念,并作為繼承結(jié)構(gòu)頂層的基類,然后以它為接口訪問派生類對象。那些在基類中無法實現(xiàn)的成員函數(shù),在派生類中卻有具體的實現(xiàn)方法。在面向?qū)ο蟪绦蛟O(shè)計語言中,用純虛函數(shù)來表示這類函數(shù)。具有純虛函數(shù)的類就稱為抽象類。5.4純虛函數(shù)和抽象類

1、純虛函數(shù)的概念純虛函數(shù)是指并無實現(xiàn)代碼,在聲明時被初始化為0的類成員函數(shù)。2、純虛函數(shù)的聲明形式classX{……

virtualreturnTypefuncName(param)=0;}3、抽象類與純虛函數(shù)的關(guān)系

只要含有純虛函數(shù)(無論是一個,還是多個)的類就是抽象類。5.4.1純虛函數(shù)和抽象類

4、C++對抽象類的限定抽象類中含有純虛函數(shù),由于純虛函數(shù)沒有實現(xiàn)代碼,所以不能建立抽象類的對象。抽象類只能作為其他類的基類,又稱為抽象基類。但是,可以創(chuàng)建抽象類的指針或引用,并通過它們訪問到生類對象,實現(xiàn)運行時的多態(tài)性。如果派生類只是簡單地繼承了抽象類的純虛函數(shù),而沒有覆蓋基類的純虛函數(shù),則派生類也是一個抽象類。5.4.1純虛函數(shù)和抽象類

【例5-9】在一個圖形系統(tǒng)中,實現(xiàn)計算各種圖形面積的程序設(shè)計。問題分析:所有圖形都有面積,但只有落實到三角形、矩形等具體圖形時才能夠計算出它的面積。設(shè)計抽象類Figure來表示圖形這一概念,并為它設(shè)置純虛函數(shù)area計算圖形的面積。

圓、三角形、矩形等具體圖形則從Figure派生,由它們提供純虛函數(shù)area的實現(xiàn)版本。借助于虛函數(shù),就可以通過Figure的指針或引用訪問到圓柱體、球體等派生類實現(xiàn)的面積函數(shù)。5.4.1純虛函數(shù)和抽象類

//Eg5-9.cpp#include<iostream>usingnamespacestd;classFigure{protected:doublex,y;public:voidset(doublei,doublej){x=i;y=j;}virtualvoidarea()=0; //純虛函數(shù)};classTriangle:publicFigure{public:voidarea(){cout<<"三角形面積:“<<x*y*0.5<<endl;}//重寫基類純虛函數(shù)};5.4.1純虛函數(shù)和抽象類

classRectangle:publicFigure{public:voidarea(inti){cout<<"這是矩形,它的面積是:"<<x*y<<endl;}};voidmain(){Figure*pF;//Figuref1; //L1,錯誤,不能定義抽象類的對象//Rectangler; //L2,錯誤,矩形的area函數(shù)不是基類虛函數(shù)

的覆蓋版本,它們的參數(shù)表不一致。Rectangle仍然是抽象類

Trianglet; //L3t.set(10,20);

pF=&t;pF->area(); //L4Figure&rF=t;rF.set(20,20);rF.area(); //L5}5.4.1純虛函數(shù)和抽象類

抽象類作為訪問派生類的接口在設(shè)計類的繼承結(jié)構(gòu)時,可以把各派生類都需要的功能設(shè)計成抽象基類的虛函數(shù),每個派生類根據(jù)自己的情況重新定義虛函數(shù)的功能,以便描述每個類特有的行為。由于抽象基類具有各派生類成員函數(shù)的虛函數(shù)版本,可以把它作為訪問整個繼承結(jié)構(gòu)的接口,通過抽象基類的指針或引用訪問在各個派生類中實現(xiàn)的虛函數(shù),這種方式也稱為接口重用,即不同的派生類都可以把抽象基類作為接口,讓其他程序通過此接口訪問各派生類的功能。多態(tài)從外部看:同一方法(函數(shù))作用不同對象時,導(dǎo)致不同行為發(fā)生從內(nèi)部看:單接口、多實現(xiàn)好處代碼重用軟件功能局部的修改和替代抽象手段(抽象類)5.4.2抽象類的應(yīng)用5.4.2抽象類的應(yīng)用抽象類的主要用途——作接口以Base的任何派生類對象作實參調(diào)用pf函數(shù),將訪問實參對象所在類的vf1,vf2,vf3等函數(shù)。Base實際是pf函數(shù)訪問各派生類成員函數(shù)的接口5.4.2抽象類的應(yīng)用【例5-10】擴(kuò)展例5-9圖形面積和體積的程序功能,用接口與實現(xiàn)分離的方式計算點、圓、圓柱體幾種圖形每種圖形的面積和體積,并且要求輸出各種圖形的類名字及各類定義對象的數(shù)據(jù)成員。問題分析點、圓、圓柱體、三角形、四邊形等都是幾何圖形,它們都具有共性,比如有類型名,有面積、體積和周長等。但當(dāng)沒有具體到某種形狀時,又無法計算,僅僅是個概念,但又確實存在,適合用純虛函數(shù)和抽象類來描述它們。用類Shape表示幾何圖形這一概念,把各類圖形計算面積、體積的函數(shù)設(shè)置成它的虛成員函數(shù)area和volume,并設(shè)置純虛函數(shù)printShapeName和print輸出圖形類型、面積、體積、圓半徑等數(shù)據(jù)。將點、圓、圓柱體等具體圖形抽象成類Point、Circle、Cylinder,它們從Shape類派生,每個類都據(jù)自己的實情重定義從Shape繼承到的area、volume等純虛函數(shù)。以Shape為接口,通過Shape的指針或引用能夠訪問到Point、Circle等派生類實現(xiàn)的area、volume等覆蓋函數(shù)版本,實現(xiàn)各圖形的面積和體積計算,以及各類圖形數(shù)據(jù)輸出等功能。5.4.2抽象類的應(yīng)用抽象結(jié)果的類圖PointCircleCylinderShapepointPoint()setPoint()getX()getY()

x,yCircleCircle()setRadius()x,y,radiusarea()volume()printShapeName()print()CylinderCylinder()setHeight()getVolumex,y,radius,hshapeShape只是概念上的幾何圖形,永遠(yuǎn)不會有稱為shape的對象存在,它的存在只是為了提供point,circle,cylinder的公有接口。所以shape的成員函數(shù)定義為:area(){return0;}volume(){return0;}printShapeName()=0;Print()=0;5.4.2抽象類的應(yīng)用抽象結(jié)果三種幾何圖形的成員:紅字是必須重定義的虛函數(shù)printName()print()pointPoint()setPoint()getX()getY()

X,yarea()printName()print()CircleCircle()setRadius()X,y,radiusarea()volume()printName()print()CylinderCylinder()setHeight()getVolumeX,y,radiusShape.h#ifndefSHAPE_H#defineSHAPE_H#include<iostream.h>

classShape{public:

virtualdoublearea()const{return0.0;}

virtualdoublevolume()const{return0.0;}

virtualvoidprintShapeName()const=0;

virtualvoidprint()const=0;};

#endif

Shape.cpp不需要此源文件,因為沒有函數(shù)要定義Point.h#ifndefPOINT_H#definePOINT_H#include"shape.h"

classPoint:publicShape{public:Point(int=0,int=0);

voidsetPoint(int,int);

intgetX()const{returnx;}intgetY()const{returny;}

virtualvoidprintShapeName()const{cout<<"Point:";}

virtual

voidprint()const;

private:intx,y;};

#endifPoint.cpp#include"point.h"

Point::Point(inta,intb){setPoint(a,b);}

voidPoint::setPoint(inta,intb){x=a;y=b;}

voidPoint::print()const{cout<<'['<<x<<","<<y<<']';}Circle.h

#ifndefCIRCLE_H#defineCIRCLE_H#include"point.h"

classCircle:publicPoint{public:Circle(doubler=0.0,intx=0,inty=0);

voidsetRadius(double);

doublegetRadius()const;

virtualdoublearea()const;

virtualvoidprintShapeName()const{cout<<"Circle:";}

virtualvoidprint()const;

private: doubleradius;//radiusofCircle};

#endifCircle.cpp#include"circle.h"

Circle::Circle(doubler,inta,intb):Point(a,b){setRadius(r);}

voidCircle::setRadius(doubler){radius=r>0?r:0;}

doubleCircle::getRadius()const{returnradius;}

doubleCircle::area()const{return3.14159*radius*radius;}

voidCircle::print()const{Point::print();cout<<";Radius="<<radius;}

Cylinder.h#ifndefCYLINDR_H#defineCYLINDR_H#include"circle.h"

classCylinder:publicCircle{public: Cylinder(doubleh=0.0,doubler=0.0, intx=0,inty=0);

voidsetHeight(double);

doublegetHeight();

virtual

doublearea()const;

virtualdoublevolume()const;

virtual

voidprintShapeName()const{cout<<"Cylinder:";}

virtualvoidprint()const;private: doubleheight;};

#endifCylinder.cpp#include"cylinder.h"

Cylinder::Cylinder(doubleh,doubler,intx,inty):Circle(r,x,y){setHeight(h);}

voidCylinder::setHeight(doubleh){height=h>0?h:0;}doubleCylinder::getHeight(){returnheight;}

doubleCylinder::area()const{return2*Circle::area()+2*3.14159*getRadius()*height;}

doubleCylinder::volume()const{returnCircle::area()*height;}

voidCylinder::print()const{Circle::print();cout<<";Height="<<height;}

main.cpp#include<iostream.h>#include<iomanip.h>#include"shape.h"#include"point.h"#include"circle.h"#include"cylindr.h"

voidvirtualViaPointer(constShape*);voidvirtualViaReference(constShape&);voidvirtualViaPointer(constShape*baseClassPtr){ baseClassPtr->printShapeName(); baseClassPtr->print(); cout<<"\nArea="<<baseClassPtr->area() <<"\nVolume="<<baseClassPtr->volume()<<"\n\n";}

voidvirtualViaReference(constShape&baseClassRef){ baseClassRef.printShapeName(); baseClassRef.print(); cout<<"\nArea="<<baseClassRef.area() <<"\nVolume="<<baseClassRef.volume()<<"\n\n";}main.cppintmain(){ cout<<setiosflags(ios::fixed|ios::showpoint) <<setprecision(2);

Pointpoint(7,11); Circlecircle(3.5,22,8); Cylindercylinder(10,3.3,10,10);

point.printShapeName(); point.print(); cout<<'\n';

circle.printShapeName(); circle.print(); cout<<'\n';

cylinder.printShapeName(); cylinder.print(); cout<<"\n\n";main.cppShape*arrayOfShapes[3];

arrayOfShapes[0]=&point;

arrayOfShapes[1]=&circle;

arrayOfShapes[2]=&cylinder;

cout<<"Virtualfunctioncallsmadeoff"<<"base-classpointers\n";

for(inti=0;i<3;i++)virtualViaPointer(arrayOfShapes[i]);

cout<<"Virtualfunctioncallsmadeoff"<<"base-classreferences\n";

for(intj=0;j<3;j++)virtualViaReference(*arrayOfShapes[j]);

return0;}main.cpp通過基類指針調(diào)用3個不同派生類對象,實現(xiàn)多態(tài)通過基類引用調(diào)用3個不同派生類對象,實現(xiàn)多態(tài)000.00.0“Point”“circle”0.0[x,y]∏r2[x,y],r2∏r2+2∏rh∏r2h“cylinder”[x,y]r,hAVPsnprAVPsnprAVPsnprAVPsnpr&point&circle&cylinder[0][1][2]x=7y=11x=22y=8x=10y=10r=3.50r=3.30h=10.0PointpointCirclecircleCylindercylinderShapevtblePointvtbleCirclevtbleCylindervtbleBaseclassptrarrayofShape12345圖中虛線為調(diào)用:baseclassPtr->printShapeName();的過程1、將&circle傳入baseClassPtr2、訪問Cirlce對象3、訪問circle

vtable4、訪問printShapeName指針,在Vtb中5、執(zhí)行printShapeName(對circle)5.4.2抽象類的應(yīng)用軟件開發(fā)工廠模式基本概念與實現(xiàn)原理工廠模式是利用面向?qū)ο笳Z言中的抽象類、繼承等技術(shù)解藕程序程度的一種軟件開發(fā)方式。具體地講,實現(xiàn)最簡單的工廠模式需要設(shè)計三種位于不同層面的類來模仿工廠產(chǎn)品的生產(chǎn)方式:抽象產(chǎn)品類(作為具體產(chǎn)品的基類,包含共有的可顯示自身產(chǎn)品信息的純虛函數(shù),其析構(gòu)函數(shù)也需要定義為虛函數(shù));具體產(chǎn)品類(實現(xiàn)從抽象產(chǎn)品類繼承來的虛函數(shù),生產(chǎn)實際的“產(chǎn)品”);

抽象工廠類(提供生產(chǎn)具體產(chǎn)品的方法)。工廠模式的優(yōu)點對外隱藏了系統(tǒng)內(nèi)部的創(chuàng)建過程,在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,而是使用一個共同的接口來訪問新創(chuàng)建的對象,有利于軟件結(jié)構(gòu)優(yōu)化和功能擴(kuò)展。//Eg5-11.cpp#include<iostream>#include<memory>usingnamespacestd;usingPRODUCT=enum{CIRCLE,SQUARE//……//L1,其他更多類型的幾何圖形“產(chǎn)品”};classShape{/L2,抽象產(chǎn)品接口類public:virtualdoublearea(double)=0;virtual~Shape(){}//L3,析構(gòu)函數(shù)};classCircle:publicShape{//L4,具體產(chǎn)品類Circledoubler=0;public:virtualdoublearea(double){ return3.14*r*r; }};classSquare:publicShape{//L5,具體產(chǎn)品類Squaredoublel=0;public:virtualdoublearea(doublel){returnl*l;}};classFactory{//L6,工廠類

public:unique_ptr<Shape>MakeProduct(PRODUCTproduct){//L7,生產(chǎn)產(chǎn)品的函數(shù)

unique_ptr<Shape>ptr=nullptr;switch(product){//L8,產(chǎn)品的清單

caseCIRCLE:ptr=unique_ptr<Circle>(newCircle);break; //L8,生產(chǎn)Circle對象

caseSQUARE:ptr=unique_ptr<Square>(newSquare);break; //L9,生產(chǎn)Square對象

}returnptr;//L10,返回生產(chǎn)的產(chǎn)品指針

}};};【例5-11】設(shè)計簡單工廠模式計算幾何圖形的面積intmain(){Factoryfactory;//L11,建立工廠

unique_ptr<Shape>produceptr=nullptr;inti;cout<<"inputproduct,1:circle,2:squqre,……"<<endl; cin>>i;switch(i){//L12,根據(jù)輸入生產(chǎn)產(chǎn)品

case1: //L13,生產(chǎn)Circleproduceptr=factory.MakeProduct(CIRCLE);cout<<"cirlcearea=“<<produceptr->area(3.0)<<endl;;break;case2://L14,生產(chǎn)Squareproduceptr=factory.MakeProduct(SQUARE);cout<<"squarearea=“<<produceptr->area(4.0)<<endl;;break;default:break;}}運行結(jié)果inputproduct,1:circle,2:squqre,……

2//輸入2squarearea=16//創(chuàng)建正方形【例5-11】設(shè)計簡單工

溫馨提示

  • 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)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論