《C++面向?qū)ο蟪绦蛟O(shè)計》-第6章-多態(tài)_第1頁
《C++面向?qū)ο蟪绦蛟O(shè)計》-第6章-多態(tài)_第2頁
《C++面向?qū)ο蟪绦蛟O(shè)計》-第6章-多態(tài)_第3頁
《C++面向?qū)ο蟪绦蛟O(shè)計》-第6章-多態(tài)_第4頁
《C++面向?qū)ο蟪绦蛟O(shè)計》-第6章-多態(tài)_第5頁
已閱讀5頁,還剩151頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第6章多態(tài)6.1多態(tài)的形式:靜態(tài)多態(tài)、動態(tài)多態(tài) 6.2一個典型例子 6.3虛函數(shù)和多態(tài):虛函數(shù)、動態(tài)聯(lián)編、多態(tài)的實現(xiàn)、構(gòu)造函數(shù)中調(diào)用virtual函數(shù)、普通成員函數(shù)中調(diào)用虛函數(shù)、私有虛函數(shù)、虛析構(gòu)函數(shù)、非虛接口〔Non-VirtualInterface〕、有默認參數(shù)的虛函數(shù)、虛函數(shù)和友元、虛函數(shù)與重載函數(shù)的比較第6章多態(tài)6.4純虛函數(shù)和抽象類:純虛函數(shù)和定義、繼承的局限、接口的繼承和實現(xiàn)繼承、裝飾模式〔Decorator〕6.5多態(tài)增強程序可擴充性的例子6.6dynamic_cast和static_cast6.7typeid獲取運行時類型信息6.8多重繼承和虛函數(shù)6.9C語言實現(xiàn)多態(tài) 多態(tài)〔polymorphism〕一詞最初來源于希臘語polumorphos,含義是具有多種形式或形態(tài)的情形。在程序設(shè)計領(lǐng)域,一個廣泛認可的定義是“一種將不同的非凡行為和單個泛化記號相關(guān)聯(lián)的能力”。和純粹的面向?qū)ο蟪绦蛟O(shè)計語言不同,C++中的多態(tài)有著更廣泛的含義。除了常見的通過類繼續(xù)和虛函數(shù)機制生效于運行期的動態(tài)多態(tài)〔dynamicpolymorphism〕外,模板也容許將不同的非凡行為和單個泛化記號相關(guān)聯(lián),由于這種關(guān)聯(lián)處理于編譯期而非運行期,因此被稱為靜態(tài)多態(tài)〔staticpolymorphism〕。6.1多態(tài)的形式6.1.1靜態(tài)多態(tài)常見的靜態(tài)方式實現(xiàn)多態(tài)有3種方法:〔1〕函數(shù)多態(tài);〔2〕宏多態(tài);〔3〕模板多態(tài)。一、函數(shù)多態(tài)函數(shù)的多態(tài)主要表現(xiàn)為函數(shù)和運算符的重載,函數(shù)重載和運算符重載可通過使用同樣的函數(shù)名和同樣的運算符來完成不同的數(shù)據(jù)處理與操作。二、宏多態(tài)帶變量的宏多態(tài)可實現(xiàn)一種初級形式的靜態(tài)多態(tài)。例6.1#include<iostream>#include<string>usingnamespacestd;#define__ADD(A,B)(A)+(B)//定義泛化的宏__ADDvoidmain(){ stringc("hello"),d("world!"); strings=__ADD(c,d); //兩個字符串“相加” cout<<s<<"\n"; inta(1),b(5); inti=__ADD(a,b); //兩個整數(shù)相加 cout<<i<<"\n";}三、模板多態(tài)例6.2#include<iostream>#include<vector>usingnamespacestd;classCar{public: voidrun()const{cout<<"runacar"<<endl; }};classAirplane{public: voidrun()const{cout<<"runaairplane"<<endl;}};template<typenameVehicle>voidrun_vehicle(constVehicle&vehicle){ vehicle.run();}intmain(){ Carcar; Airplaneairplane; run_vehicle(car);//調(diào)用Car::run() run_vehicle(airplane);//調(diào)用Airplane::run() return0;}例6.3#include<iostream>#include<vector>usingnamespacestd;classCar{public: voidrun()const{cout<<"runacar"<<endl; }};classAirplane{public: voidrun()const{cout<<"runaairplane"<<endl;}};//run同質(zhì)vehicles集合template<typenameVehicle>voidrun_vehicles(constvector<Vehicle>&vehicles){for(unsignedinti=0;i<vehicles.size();++i){vehicles[i].run();//根據(jù)vehicle的具體類型調(diào)用相應(yīng)的run()}}intmain(){Carcar1,car2;Airplaneairplane1,airplane2;vector<Car>vc;//同質(zhì)cars集合vc.push_back(car1);vc.push_back(car2);vc.push_back(airplane1);//errorC2664:“std::vector<_Ty>::push_back”:不能將參數(shù)1從“Airplane”轉(zhuǎn)換為“constCar&”run_vehicles(vc);//runcarsvector<Airplane>vs;//同質(zhì)airplanes集合vs.push_back(airplane1);vs.push_back(airplane2);vs.push_back(car1);//errorC2664:“std::vector<_Ty>::push_back”:不能將參數(shù)1從“Car”轉(zhuǎn)換為“constAirplane&”run_vehicles(vs);//runairplanes}6.1.2動態(tài)多態(tài)

C++的動態(tài)多態(tài)性是C++實現(xiàn)面向?qū)ο蠹夹g(shù)的根底。具體的說,通過一個指向基類的指針調(diào)用虛成員函數(shù)的時候,運行時系統(tǒng)將能夠根據(jù)指針所指向的實際對象調(diào)用恰當?shù)某蓡T函數(shù)實現(xiàn)。例6.4#include<iostream>#include<vector>usingnamespacestd;classVehicle{public:virtualvoidrun()const=0;};classCar:publicVehicle{public:virtualvoidrun()const{cout<<"runacar"<<endl;}};classAirplane:publicVehicle{public:virtualvoidrun()const{cout<<"runaairplane"<<endl;}};//run異質(zhì)vehicles集合voidrun_vehicles(constvector<Vehicle*>&vehicles){for(unsignedinti=0;i<vehicles.size();++i){vehicles[i]->run();//根據(jù)具體vehicle的類型調(diào)用對應(yīng)的run()}}intmain(){ Carcar; Airplaneairplane; vector<Vehicle*>v;//異質(zhì)vehicles集合 v.push_back(&car); v.push_back(&airplane); run_vehicles(v);//run不同類型的vehicles return0;}6.2一個典型例子根據(jù)賦值兼容,用基類類型的指針指向派生類,就可以通過這個指針來使用派生類的成員函數(shù)。如果這個函數(shù)是普通的成員函數(shù),通過基類類型的指針訪問到的只能是基類的同名成員。而如果將它設(shè)置為虛函數(shù),那么可以使用基類類型的指針訪問到指針正在指向的派生類的同名函數(shù)。這樣,通過基類類型的指針,就可以使屬于不同派生類的不同對象產(chǎn)生不同的行為,從而實現(xiàn)運行過程的多態(tài)。例6.5#include<iostream>usingnamespacestd;classA{public: voidPrint(){cout<<“A::Print”<<endl;}};classB:publicA{public:

voidPrint(){cout<<“B::Print”<<endl;}};intmain(){ Aa; Bb; A*pA=&b; pA->Print(); return0;}程序執(zhí)行結(jié)果: A::Print通過指針調(diào)用成員函數(shù)只與指針類型有關(guān),與此刻指向的對象無關(guān)?;愔羔槦o論指向基類還是派生類對象,利用pA->Print()調(diào)用的都是基類成員函數(shù)Print()。假設(shè)要調(diào)用派生類中的成員函數(shù)Print()必須通過對象來調(diào)用,或定義派生類指針實現(xiàn)。這種通過用戶自己指定調(diào)用成員函數(shù),在編譯時根據(jù)類對象來確定調(diào)用該類成員函數(shù)的方式,是靜態(tài)聯(lián)編。假設(shè)將A類中的Print()函數(shù)聲明為virtual,那么此時就為動態(tài)聯(lián)編程序執(zhí)行結(jié)果為: B::Print6.3虛函數(shù)和多態(tài)虛函數(shù)

一、虛函數(shù)定義只有類的成員函數(shù)才能說明為虛函數(shù),因為虛函數(shù)僅適合用與有繼承關(guān)系的類對象,所以普通函數(shù)不能說明為虛函數(shù)。在類的定義中,前面有virtual關(guān)鍵字的成員函數(shù)就是虛函數(shù)。classbase{ virtualvoidget();}voidbase::get(){}virtual關(guān)鍵字只用在類定義里的函數(shù)聲明中,寫函數(shù)體時不用。假設(shè)寫成:virtualvoidbase::get(){}那么編譯時會提示: errorC2723:“base::get”:“virtual”存儲類說明符在函數(shù)定義上非法構(gòu)造函數(shù)和靜態(tài)成員函數(shù)不能是虛函數(shù):靜態(tài)成員函數(shù)不能是虛函數(shù),因為靜態(tài)成員函數(shù)沒有this指針,是不受限制于某個對象;構(gòu)造函數(shù)不能是虛函數(shù),因為構(gòu)造的時候,對象還是一片位定型的空間,只有構(gòu)造完成后,對象才是具體類的實例。例6.6classA{public: virtualA(){};//errorC2633:“A”:“inline”是構(gòu)造函數(shù)的唯一合法存儲類};classB{public: virtualstaticvoidfunb(){};//errorC2216:“virtual”不能和“static”一起使用};intmain(){ return0;}二、多態(tài)的兩種形式派生類對象的指針可以直接賦值給基類指針: ptrBase=&objDerived;ptrBase指向的是一個Derived類的對象。*ptrBase可以看作一個Base類的對象,訪問它的public成員。直接通過ptrBase,不能夠訪問objDerived由Derived類擴展的成員通過強制指針類型轉(zhuǎn)換,可以把ptrBase轉(zhuǎn)換成Derived類的指針:

ptrBase=&objDerived;

ptrDerived=static_cast<Derived*>ptrBase;當編譯器看到通過指針或引用調(diào)用virtual函數(shù)時,對其執(zhí)行晚綁定,即通過指針〔或引用〕指向的類的類型信息來決定該函數(shù)是哪個類的。通常此類指針或引用都聲明為基類的,它可以指向基類或派生類的對象。通過基類指針或引用調(diào)用基類和派生類中的同名虛函數(shù)時,假設(shè)指向一個基類的對象,那么被調(diào)用是基類的虛函數(shù);如果指向一個派生類的對象,那么被調(diào)用的是派生類的虛函數(shù),這種機制就叫做“多態(tài)”。例6.7classB{public: virtualvoidprint(){cout<<"HelloB"<<endl;}};classD:publicB{public: virtualvoidprint(){cout<<"HelloD"<<endl;}};intmain(){ Dd; B*pb=&d; pb->print(); B&rb=d; rb.print(); return0;}指向基類的指針,可以指向它的公有派生的對象,但不能指向私有派生的對象,對于引用也是一樣的。假設(shè)上例D為私有派生與B,那么: B*pb=&d;//errorC2243:“類型轉(zhuǎn)換”:從“D*”到“B*”的轉(zhuǎn)換存在,但無法訪問 B&rb=d;//errorC2243:“類型轉(zhuǎn)換”:從“D*”到“B&”的轉(zhuǎn)換存在,但無法訪問三、多態(tài)的例子李氏兩兄妹〔哥哥和妹妹〕參加姓氏運動會〔不同姓氏組隊參加〕,哥哥男子工程比賽,妹妹參加女子工程比賽,開幕式有一個參賽隊伍代表發(fā)言儀式,兄妹倆都想去露露臉,可只能一人去,最終他們決定到時抓鬮決定,而組委會也不反對,它才不關(guān)心是哥哥還是妹妹來發(fā)言,只要派一個姓李的來說兩句話就行。例6.8classLi{public:virtualsay(){}};classLi_brother:publicLi{public: virtualsay() { cout<<"I'mLiming,Ihavethreesports:boxing,fencingandwrestling."<<endl; } voidboxing(){cout<<"boxing"<<endl;} voidfencing(){cout<<"fencing"<<endl;} voidwrestling(){cout<<"wrestling"<<endl;}};classLi_sister:publicLi{public: virtualsay(){cout<<"I'mLixia,Ihavetwosports:swimandskating."<<endl;} voidswim(){cout<<"swim"<<endl;} voidskating(){cout<<"skating"<<endl;}};voidSpeak(Li*pL){pL->say();}intmain(){ Li*p;//基類指針,李家代表 Li_brotherb; Li_sisters; p=&s;//基類指針指向派生類Li_sister對象,獲得發(fā)言時機 Speak(p); return0;}6.3.2動態(tài)聯(lián)編

普通函數(shù)重載與靜態(tài)聯(lián)編:成員函數(shù)重載與靜態(tài)聯(lián)編:classA{public: virtualvoidGet();};classB:publicA{public: virtualvoidGet();};voidMyFunction(A*pa){ pa->Get();}pa->Get()調(diào)用的是A::Get()還是B::Get(),編譯時無法確定,因為不知道MyFunction被調(diào)用時,形參會對應(yīng)于一個A對象還是B對象。所以只能等程序運行到pa->Get()了,才能決定到底調(diào)用哪個Get()。動態(tài)聯(lián)編的實現(xiàn)需要如下三個條件:〔1〕要有說明的虛函數(shù);〔2〕調(diào)用虛函數(shù)操作的是指向?qū)ο蟮闹羔樆蛘邔ο笠?;或者是由成員函數(shù)調(diào)用虛函數(shù);〔3〕派生類型關(guān)系的建立。例6.9classB{public: virtualprint(){cout<<"HelloB"<<endl;}};classD:publicB{public: virtualprint(inti){cout<<"HelloD,i="<<i<<endl;}};intmain(){ Dd; B*p=&d; p->print(); return0;}程序執(zhí)行結(jié)果為: HelloB假設(shè)將“p->print();”改為: p->print(5);//errorC2660:“B::print”:函數(shù)不接受1個參數(shù)如果虛函數(shù)在基類與派生類中出現(xiàn),僅僅是名字相同,而形式參數(shù)不同,那么即使加上了virtual關(guān)鍵字,也是不會進行動態(tài)聯(lián)編的。6.3.3多態(tài)的實現(xiàn)

多態(tài)性的實現(xiàn)要求我們增加一個間接層,在這個間接層中攔截對于方法的調(diào)用,然后根據(jù)指針所指向的實際對象調(diào)用相應(yīng)的方法實現(xiàn)。在這個過程中,增加的這個間接層非常重要,它要完成以下幾項工作:〔1〕獲知方法調(diào)用的全部信息,包括被調(diào)用的是哪個方法,傳入的實際參數(shù)有哪些?!?〕獲知調(diào)用發(fā)生時指針〔引用〕所指向的實際對象?!?〕根據(jù)第前兩步獲得的信息,找到適宜的方法實現(xiàn)代碼,執(zhí)行調(diào)用。例6.10classBase{public: inti; virtualvoidPrint_1(){cout<<"Base:Print_1";} virtualvoidPrint_2(){cout<<"Base:Print_2";}};classDerived:publicBase{public: intn; virtualvoidPrint_1(){cout<<"Drived:Print_1"<<endl;} virtualvoidPrint_2(){cout<<"Drived:Print_2"<<endl;}};intmain(){ Derivedd; cout<<sizeof(Base)<<","<<sizeof(Derived); return0;}程序運行輸出結(jié)果: 8,12每一個有虛函數(shù)的類〔或有虛函數(shù)的類的派生類〕都有一個虛函數(shù)表,該類的任何對象中都放著虛函數(shù)表的指針。虛函數(shù)表中列出了該類的虛函數(shù)地址。多出來的4個字節(jié)就是用來放虛函數(shù)表的地址的。6.3.4構(gòu)造函數(shù)中調(diào)用virtual函數(shù)

例6.13classTransaction{public: Transaction(){logTransaction();} virtualvoidlogTransaction()=0;};classBuyTransaction:publicTransaction{public: intbuyNum; virtualvoidlogTransaction(){cout<<"ThisisaBuyTransaction";}};classSellTransaction:publicTransaction{public: intsellNum; virtualvoidlogTransaction() { cout<<"ThisisaSellTransaction"; }};voidmain(){ BuyTransactionb; SellTransactions;}這個程序可以通過編譯,但鏈接有錯:errorLNK2019:無法解析的外部符號"public:virtualvoid__thiscallTransaction::logTransaction(void)"(?logTransaction@Transaction@@UAEXXZ),該符號在函數(shù)"public:__thiscallTransaction::Transaction(void)"(??0Transaction@@QAE@XZ)中被引用假設(shè)將基類的Transaction中虛函數(shù)logTransaction改為:virtualvoidlogTransaction(){ cout<<"ThisisaTransaction"<<endl;};程序執(zhí)行結(jié)果為: ThisisaTransaction ThisisaTransaction在構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用虛函數(shù)時:他們調(diào)用的函數(shù)是自己的類或基類中定義的函數(shù),不會等到運行時才決定調(diào)用自己的還是派生類的函數(shù)。例6.14classmyclass{public: virtualvoidhello(){cout<<"hellofrommyclass"<<endl;}; virtualvoidbye(){cout<<"byefrommyclass"<<endl;};};classson:publicmyclass{public: voidhello(){cout<<"hellofromson"<<endl;}; son(){hello();}; ~son(){bye();};};classgrandson:publicson{public: voidhello(){cout<<"hellofromgrandson"<<endl;}; grandson(){cout<<"constructinggrandson"<<endl;}; ~grandson(){cout<<"destructinggrandson"<<endl;};};intmain(){ grandsongson; son*pson; pson=&gson; pson->hello();//voidgrandson::hello() return0;}程序執(zhí)行結(jié)果:hellofromson//gson先創(chuàng)立son,son()中靜態(tài)連接了son::hello()constructinggrandson//gson的創(chuàng)立hellofromgrandson//pson->hello()動態(tài)連接到grandson::hello()destructinggrandson//gson的創(chuàng)立的析構(gòu)byefrommyclass//gson中son局部的析構(gòu),~son()中靜態(tài)連接了myclass::bye()6.3.5普通成員函數(shù)中調(diào)用虛函數(shù)

在普通成員函數(shù)中調(diào)用虛函數(shù),那么是動態(tài)聯(lián)編,是多態(tài)。例6.16#include<iostream>usingnamespacestd;classBase{public: voidfunc1(){func2();} voidvirtualfunc2(){cout<<"Base::func2()"<<endl;}};classDerived:publicBase{public: virtualvoidfunc2(){cout<<"Derived:func2()"<<endl;}};intmain(){ Derivedd; Base*pBase=&d; pBase->func1(); return0;}因為,Base類的fun1()為非靜態(tài)成員函數(shù),編譯器會給加一個this指針:相當于voidfun1(){ this->fun2();}編譯這個函數(shù)的代碼的時候,由于fun2()是虛函數(shù),this是基類指針,所以是動態(tài)聯(lián)編。上面這個程序運行到fun1函數(shù)中時,this指針指向的是d,所以經(jīng)過動態(tài)聯(lián)編,調(diào)用的是Derived::fun2()。6.3.6私有虛函數(shù)

一、虛函數(shù)的訪問權(quán)限例6.17classBase{private: virtualvoidfunc(){cout<<"Base::func()"<<endl;}};classDerived:publicBase{public: virtualvoidfunc(){cout<<"Derived:func()"<<endl;}};intmain(){ Derivedd; Base*pBase=&d; pBase->func();//errorC2248:“Base::func”:無法訪問private成員(在“Base”類中聲明) return0;}二、非公有虛函數(shù)通過基類指針或引用調(diào)用成員函數(shù)時,不管成員函數(shù)的可見性如何〔public、protected或private都可以〕,如果該函數(shù)時非虛的,那么將采用靜態(tài)綁定,即編譯時綁定;如果該函數(shù)是虛擬的,那么采用動態(tài)綁定,即運行時綁定。例6.18classB{private:virtualvoidfunc2(){cout<<"B::func2"<<endl;}public: voidfunc1() { func2(); }};classD:publicB{private: virtualvoidfunc2(){cout<<"D::func2"<<endl;}};intmain(){ B*p=newD(); p->func1(); deletep; return0;}三、模板方法模式在理解了上面所述的內(nèi)容的情況下,再來理解模板方法模式就非常容易了,模板方法定義了一個操作中的算法的骨架,而將一些步驟的實現(xiàn)延遲到派生類中,模板方法使得派生類可以不改變一個算法的結(jié)構(gòu)即可重定義算法的某些特定步驟。classBaseTemplate{private: voidstep1(){…}//不可被更改的實現(xiàn)細節(jié) virtualvoidstep2(){…}//可以被派生類修改的實現(xiàn)細節(jié)virtualvoidstep3()=0;//必須被派生類修改的實現(xiàn)細節(jié)public: voidwork()//骨架函數(shù),實現(xiàn)了骨架 { step1(); step2(); step3(); }};6.3.7虛析構(gòu)函數(shù)

通過基類的指針刪除派生類對象時,通常情況下只調(diào)用基類的析構(gòu)函數(shù)。但是,刪除一個派生類的對象時,應(yīng)該先調(diào)用派生類的析構(gòu)函數(shù),然后調(diào)用基類的析構(gòu)函數(shù)。例6.20classfather{public: ~father(){cout<<"byefromfather"<<endl;};};classson:publicfather{public: ~son(){cout<<"byefromson"<<endl;};};intmain(){ father*pfather; pfather=newson; deletepfather; return0;}程序執(zhí)行結(jié)果為: byefromfather。沒有執(zhí)行son::~son()!!!將上面程序father類的析構(gòu)函數(shù)聲明為虛的: virtual~father(){cout<<"byefromfather"<<endl;};那么程序執(zhí)行結(jié)果為: byefromgrandson byefromson6.3.8非虛接口〔Non-VirtualInterface〕假設(shè)我們?yōu)槟骋唤M對象提供了一個抽象的標準,其中有一個方法,需要被該對象內(nèi)部調(diào)用,因此不需要對外開放。但是該方法在不同的對象內(nèi)的行為是不同的,這就需要不同的對象給出自己的實現(xiàn)。這種情況下,私有的純虛函數(shù)是非常好的選擇。例6.21classBase{public:voidfunc1(){func2();};private: virtualvoidfunc2(){cout<<"Base::func2()"<<endl;}};classDerived:publicBase{private: virtualvoidfunc2(){cout<<"Derived:func2()"<<endl;}};intmain(){ Derivedd; Base*pBase=&d; pBase->func1(); return0;}NVI提供一個公有的非虛接口函數(shù),將虛函數(shù)私有化。實現(xiàn)行為和接口的別離。因為虛函數(shù)的多態(tài)性,公有非虛函數(shù)自然會去調(diào)用相應(yīng)的虛函數(shù)實現(xiàn)。通過對虛函數(shù)的包裝到達對接口與實現(xiàn)別離的效果。函數(shù)func1定義了接口的形式,而func2()函數(shù)那么實現(xiàn)了對func1函數(shù)的行為定制,實現(xiàn)了接口定義和實現(xiàn)的別離。6.3.9有默認參數(shù)的虛函數(shù)

例6.22classA{public: virtualvoidshow(constchar*conststr="A") { cout<<"ThisisAstr="<<str<<endl; }};classB:publicA{public: virtualvoidshow(constchar*conststr="B") { cout<<"ThisisBstr="<<str<<endl; }};classC:publicA{public: virtualvoidshow(constchar*conststr="C") { cout<<"ThisisCstr="<<str<<endl; }};intmain(){ A*pa; Aa; Bb; Cc; pa=&a; pa->show(); pa=&b; pa->show(); pa=&c; pa->show(); return0;}虛函數(shù)是動態(tài)綁定的〔即在運行時〕,但缺省參數(shù)是靜態(tài)綁定的〔即在編譯時〕,默認參數(shù)在編譯的時候已經(jīng)寫死了,不會動態(tài)的。這意味著你最終可能想調(diào)用的是一個派生類中的虛函數(shù),但使用了基類中的缺省參數(shù)值的虛函數(shù)。6.3.10虛函數(shù)和友元

例6.23classB;classA{ voidprint(){cout<<"A::print"<<endl;}public: friendclassB;};classB{public: voidfunc(Aa){a.print();}};classD:publicB{};intmain(){ Aa; Dd; d.func(a); return0;}程序執(zhí)行結(jié)果為:

A::print一個友元類的派生類,可以通過其基類接口去訪問設(shè)置其基類為友元類的類的私有成員,也就是說一個類的友元類的派生類,某種意義上還是其友元類。因此更準確地說友元不能繼承例6.25classA;classB{ intb; voidprint(){cout<<b<<endl;}public: B(intx=0){b=x;} friendclassA;};classA{ inta;public: voidfun(Bb){b.print();}};classD:publicB{public:

D(intx):B(x){}};intmain(){ cout<<sizeof(A)<<""<<sizeof(B)<<""<<sizeof(D); Dd(99); Aa; a.fun(d); return0;}程序執(zhí)行結(jié)果為: 444 99友元類、基類和派生類大小都是4,友元類A不是基類B的一局部,更不是派生類D的一局部。從上兩例看,友元視乎能夠被繼承,基類的友元函數(shù)或友元類能夠訪問派生類的私有數(shù)據(jù)!假設(shè)將上兩例中的繼承關(guān)系改為私有繼承,那么: classD:privateB a.fun(d);//errorC2243:“類型轉(zhuǎn)換”:從“D*”到“constB&”的轉(zhuǎn)換存在,但無法訪問在上一章中講到:public繼承是一種“isa”的關(guān)系,即一個派生類對象可看成一個基類對象。所以,上面兩例中不是基類的友元被繼承了,而是派生類被識別為基類了。例6.26classA;classB{ voidprint(){cout<<"B::print"<<endl;}public: friendclassA;};classA{public: voidfun(Bb) {b.print();}};classD:publicB{ voidprint(){cout<<"D::print"<<endl;}};intmain(){ Dd; Aa; a.fun(d); return0;}程序執(zhí)行結(jié)果為: B::print假設(shè)將上例print函數(shù)改為虛函數(shù)并通過多態(tài)來訪問,就可以到達類似于友元可以繼承的效果。例6.27classA;classB{ virtualvoidprint(){cout<<"B::print"<<endl;}public: friendclassA;};classA{public: voidfunc(B*pb){pb->print();}};classD:publicB{ virtualvoidprint(){cout<<"D::print"<<endl;}};intmain(){ Dd; Aa; a.func(&d); return0;}程序執(zhí)行結(jié)果為: D::print6.3.11虛函數(shù)與重載函數(shù)的比較虛函數(shù)與重載函數(shù)的比較:〔1〕重載函數(shù)要求函數(shù)有相同的返回值類型和函數(shù)名稱,并有不同的參數(shù)序列;而虛函數(shù)那么要求這三項〔函數(shù)名、返回值類型和參數(shù)序列〕完全相同。〔2〕重載函數(shù)可以是成員函數(shù)或友元函數(shù),而虛函數(shù)只能是成員函數(shù)?!?〕重載函數(shù)的調(diào)用是以所傳遞參數(shù)序列的差異作為調(diào)用不同函數(shù)的依據(jù);虛函數(shù)是根據(jù)對象的不同去調(diào)用不同類的虛函數(shù)?!?〕虛函數(shù)在運行時表現(xiàn)出多態(tài)功能,這是C++的精髓;而重載函數(shù)那么在編譯時表現(xiàn)出多態(tài)性。6.4純虛函數(shù)和抽象類6.4.1純虛函數(shù)和定義

在許多情況下,在基類中不能對虛函數(shù)給出有意義有實現(xiàn),而把它說明為純虛函數(shù)。純虛函數(shù)是沒有函數(shù)體的虛函數(shù),它的實現(xiàn)留給該基類的派生類去做,這就是純虛函數(shù)的作用。classA{private:

inta;public: virtualvoidPrint()=0;//純虛函數(shù)

voidfun(){cout<<"fun";}};帶有純虛函數(shù)的類稱為抽象類。抽象類是一種特殊的類,它是為了抽象和設(shè)計的目的而建立的,它處于繼承層次結(jié)構(gòu)的較上層。抽象類是不能定義對象的,在實際中為了強調(diào)一個類是抽象類,可將該類的構(gòu)造函數(shù)說明為保護的訪問控制權(quán)限。假設(shè)一個類的構(gòu)造函數(shù)聲明為私有的,那么該類和該類的派生類都不能創(chuàng)立對象;假設(shè)構(gòu)造函數(shù)聲明為保護型,那么該類不能創(chuàng)立對象,但它的派生類是可以創(chuàng)立對象的。例6.28classB1{protected: B1(){}};classB2{private: B2(){}};classD1:publicB1{public: D1(){}};classD2:publicB2{public: D2(){}//errorC2248:“B2::B2”:無法訪問private成員(在“B2”類中聲明)};intmain(){ B1b1;//errorC2248:“B1::B1”:無法訪問protected成員(在“B1”類中聲明) B2b2;//errorC2248:“B2::B2”:無法訪問private成員(在“B2”類中聲明) D1d1;//OK D2d2;//error return0;}抽象類只能作為基類來派生新類使用,不能創(chuàng)立抽象類的對象,但抽象類的指針和引用可以指向由抽象類派生出來的類的對象。 Aa;//錯,A是抽象類,不能創(chuàng)立對象 A*pa;//ok,可以定義抽象類的指針和引用 pa=newA;//錯誤,A是抽象類,不能創(chuàng)立對象純虛函數(shù)和空函數(shù)是不同的,假設(shè)Print()函數(shù)寫成: virtualvoidPrint(){}那么類A是可以創(chuàng)立對象的。從抽象類派生的類必須實現(xiàn)基類的純虛函數(shù),否那么該派生類也不能創(chuàng)立對象。例6.29classBase{public: virtualvoidfunc()=0;};classDerived:publicBase{}intmain(){ Derivedd;//errorC2259:“Derived”:不能實例化抽象類 return0;}例6.30classB{public: virtualvoidfunc()=0;};voidB::func(){cout<<"B::func"<<endl;}classD1:B{};classD2:B{public: virtualvoidfunc(){cout<<"D2::B"<<endl;}};intmain(){ Bb;//errorC2259:“B”:不能實例化抽象類 D1d1;//errorC2259:“D1”:不能實例化抽象類 D2d2; return0;}例6.31classB{public: virtualvoidfunc()=0;};voidB::func(){cout<<"B::func"<<endl;}classD:B{public: voidfunc() { B::func();//調(diào)用基類的實現(xiàn)代碼

}};intmain(){ Dd; d.func(); return0;}純抽象類〔接口類〕僅僅只含有純虛函數(shù)、不包含任何數(shù)據(jù)成員。classA{ virtualvoidPrint()=0;//純虛函數(shù) virtualvoidfun()=0;//純虛函數(shù)};COM組件實際上是一個C++類,而接口都是純虛類。組件從接口派生而來。classIObject{public: virtualFunction1(...)=0; virtualFunction2(...)=0; ....};classMyObject:publicIObject{public: virtualFunction1(...){...} virtualFunction2(...){...} ....};6.4.2繼承的局限

假設(shè)我們需要對四邊形、矩形、正方形進行建模,需要計算這些形狀邊長和面積??赡芪覀兊谝环错懯峭ㄟ^繼承來實現(xiàn):以四邊形作為基類,矩形和正方形依次繼承下來。這符合繼承所要求的“派生類”是一個基類,派生類是基類的特殊化。但是經(jīng)過仔細分析,就會覺察出其中的不妥。暫時不考慮繼承關(guān)系而分別實現(xiàn)三個類,那么它們是下面這個樣子的:classPoint{

intx;

inty;}classQuadrangle{public: virtualdoubleGetSideLength(); virtualdoubleGetArea(); Pointm_arrP[4];};classRectangle:Quadrangle{public: virtualdoubleGetSideLength(); virtualdoubleGetArea();};classSquare:Rectangle{public: virtualdoubleGetSideLength(); virtualdoubleGetArea();};顯然,正方形Square的實現(xiàn)最簡單,四邊形Quadrangle的實現(xiàn)最復(fù)雜。繼承機制有一個特點:派生類總是比基類更復(fù)雜,因為派生類是在完整的繼承了基類實現(xiàn)的根底上增加新的成員、方法。由四邊形到矩形再到正方形卻是越來越簡單,這就形成了一個悖論,導(dǎo)致我們無法按照繼承的層次描述三者的關(guān)系。因此最好分別實現(xiàn)這三個類,不考慮三個者之間的關(guān)系。如果確實需要描述三者之間的層次關(guān)系,那么最好的方式是使用接口classIQuadrangle{public: virtualdoubleGetSideLength()=0; virtualdoubleGetArea()=0;};classIRectangle:IQuadrangle{};classISquare:IRectangle{};classQuadrangle:IQuadrangle{public: virtualdoubleGetSideLength(); virtualdoubleGetArea(); Pointm_arrP[4];};classRectangle:IRectangle{public: virtualdoubleGetSideLength(); virtualdoubleGetArea(); Pointm_arrP[4];};classSquare:ISquare{public: virtualdoubleGetSideLength(); virtualdoubleGetArea(); Pointm_arrP[4];};6.4.3接口的繼承和實現(xiàn)繼承

公有繼承的概念實際上包含兩個相互獨立的局部:函數(shù)接口的繼承和函數(shù)實現(xiàn)的繼承。二者之間的差異恰與函數(shù)聲明和函數(shù)實現(xiàn)之間相異之處等價。有時候你會希望派生類:〔1〕只繼承成員函數(shù)的接口〔也就是聲明〕。聲明一個純虛函數(shù)〔purevirtual〕的目的就是讓派生類只繼承函數(shù)的接口。純虛函數(shù)在抽象類中沒有定義內(nèi)容,在所有具體的派生類中,必須要對它們進行實現(xiàn)。〔2〕在繼承接口的同時繼承默認的實現(xiàn),聲明一個簡單虛函數(shù),即非純虛函數(shù)〔impurevirtual〕的目的就是讓派生類繼承該函數(shù)的接口和缺省實現(xiàn)。〔3〕繼承接口和強制內(nèi)容的實現(xiàn)。例如某航空公司有兩種機型:A機型和B機型,它們飛行控制系統(tǒng)和航線是完全一致的。于是,有了這樣設(shè)計:classAirport{...};//機場類classAirplane{public: virtualvoidfly(constAirport&destination); { //默認代碼:使飛機抵達給定的目的地

}};classModelA:publicAirplane{...};classModelB:publicAirplane{...};切斷虛函數(shù)的接口和默認具體實現(xiàn)之間的聯(lián)系。classAirplane{public: virtualvoidfly(constAirport&destination)=0;protected: voiddefaultFly(constAirport&destination) { //默認代碼:使飛機抵達給定目的地

}};classModelA:publicAirplane{public: virtualvoidfly(constAirport&destination){defaultFly(destination);}};classModelB:publicAirplane{public: virtualvoidfly(constAirport&destination){defaultFly(destination);}};classModelC:publicAirplane{public: virtualvoidfly(constAirport&destination) { //使C型飛機抵達目的地的代碼 }};按照上面的方法做會產(chǎn)生一些近親函數(shù)名字,從而污染了類名字空間。那么如何解決這一問題?我們知道純虛函數(shù)在具體的派生類中必須要重新實現(xiàn),但是純虛函數(shù)自身也可以有具體實現(xiàn),借助這一點問題便迎刃而解。classAirplane{public: virtualvoidfly(constAirport&destination)=0;};voidAirplane::fly(constAirport&destination)//純虛函數(shù)的具體實現(xiàn){ //默認代碼:使飛機抵達給定的目的地}classModelA:publicAirplane{public: virtualvoidfly(constAirport&destination){Airplane::fly(destination);}};classModelB:publicAirplane{public: virtualvoidfly(constAirport&destination){Airplane::fly(destination);}};classModelC:publicAirplane{public: virtualvoidfly(constAirport&destination) { //使C型飛機抵達目的地的代碼 }};6.4.4裝飾模式〔Decorator〕在軟件系統(tǒng)中,有時候我們會使用繼承來擴展對象的功能,但是由于繼承為類型引入的靜態(tài)特質(zhì),使得這種擴展方式缺乏靈活性;并且隨著派生類的增多〔擴展功能的增多〕,各種派生類的組合〔擴展功能的組合〕會導(dǎo)致更多派生類的膨脹。如何使“對象功能的擴展”能夠根據(jù)需要來動態(tài)地實現(xiàn)?同時防止“擴展功能的增多”帶來的派生類膨脹問題?從而使得任何“功能擴展變化”所導(dǎo)致的影響將為最低?這就是Decorator模式。例如,我們要給增加一些裝飾,假設(shè)采用繼承的設(shè)計方式可能會這樣做:寫一個“裝飾”基類,然后各種小裝飾從這個基類派生。但是隨著以后擴展功能的增多〔有更多的小飾品〕,派生類會迅速的膨脹在裝飾模式中存在以下四種角色:〔1〕抽象構(gòu)件角色〔Component〕:定義一個抽象接口,來標準準備附加功能的類?!?〕具體構(gòu)件角色〔ConcreteComponent〕,即被裝飾者:將要被附加功能的類,實現(xiàn)抽象構(gòu)件角色接口?!?〕抽象裝飾者角色〔Decorator〕:持有對具體構(gòu)件角色的引用并定義與抽象構(gòu)件角色一致的接口?!?〕具體裝飾角色〔ConcreteDecorator〕:實現(xiàn)抽象裝飾者角色,負責為具體構(gòu)件添加額外功能。例6.32#include<iostream>#include<string>usingnamespacestd;classPhone//公共抽象類{public: virtualvoidShowDecorate()=0;};classiPhone:publicPhone{private: stringm_name;//名稱public: iPhone(stringname):m_name(name){} voidShowDecorate(){cout<<m_name<<"'sDecoration:"<<endl;}};classDecoratorPhone:publicPhone//裝飾類{private: Phone*m_phone;//要裝飾的public: DecoratorPhone(Phone*phone):m_phone(phone){} virtualvoidShowDecorate(){m_phone->ShowDecorate();}};classDecoratorPhoneA:publicDecoratorPhone//具體的裝飾類{public: DecoratorPhoneA(Phone*phone):DecoratorPhone(phone){} voidShowDecorate() { DecoratorPhone::ShowDecorate(); AddDecorate(); }private: voidAddDecorate(){cout<<"Pendant"<<endl;}//增加掛件};classDecoratorPhoneB:publicDecoratorPhone//具體的裝飾類{public: DecoratorPhoneB(Phone*phone):DecoratorPhone(phone){} voidShowDecorate() { DecoratorPhone::ShowDecorate(); AddDecorate(); }private: voidAddDecorate(){cout<<"Sticker"<<endl;}//增加貼圖};intmain(){ Phone*piphone=newiPhone("iphone4"); Phone*pda=newDecoratorPhoneA(piphone);//裝飾,增加掛件 Phone*pdb=newDecoratorPhoneB(pda); //裝飾,增加貼圖 pdb->ShowDecorate(); deletepda; deletepdb; deleteiphone; return0;}6.5多態(tài)增強程序可擴充性的例子例如,對各種幾何對象如圓、矩形、直線等,都有一些共同的操作,比方畫出幾何對象等,我們把這些接口抽象成虛函數(shù)放在所謂的抽象基類Shape中,具體的幾何對象類那么繼承這個抽象基類。如下:classShape//幾何對象的公共抽象基類{public:virtualvoiddraw()const=0;//畫出幾何對象};classCircle:publicShape//具體的幾何對象類:圓{public:virtualvoiddraw()const;};classLine:publicShape//直線類{public:virtualvoiddraw()const;};classRectangle:publicShape//矩形類{public:virtualvoiddraw()const;};多態(tài)是通過基類指針或引用指向派生類對象時所調(diào)用的虛函數(shù)為派生類的虛函數(shù)。Shape*p;p=newCircle;p->draw();//調(diào)用Circle::draw()p=newLine;p->draw();//調(diào)用Line::draw()p=newRectangle;p->draw();//調(diào)用Rectangle::draw()像上面這樣使用多態(tài),是沒表達出多態(tài)的好處,僅僅是對多態(tài)根本概念進行了驗證。真正能表達出多態(tài)設(shè)計的好處,應(yīng)該將基類指針做為函數(shù)參數(shù)來用。voidDrawShape(Shape*p){ p->draw();}Circle*pC=newCircle;DrawShape(pC);Line*pL=newLine;DrawShape(pL);Rectangle*pR=newRectangle;DrawShape(pR);例6.33游戲《魔法門之英雄無敵》。游戲中有很多種精靈,每種精靈都有一個類與之對應(yīng),每個精靈就是一個對象精靈能夠互相攻擊,攻擊敵人和被攻擊時都有相應(yīng)的動作,動作是通過對象的成員函數(shù)實現(xiàn)的。假設(shè)游戲中有n種精靈,CSoldier類中就會有n個Attack成員函數(shù),以及n個FightBack成員函數(shù),對于其他類,比方CKirin、CAngel、CGriffin等,也是這樣。游戲版本升級時,要增加新的精靈——半馬人〔CCentaur〕,如圖6.16所示。如何編程才能使升級時的代碼改動和增加量較?。坎还苁欠裼枚鄳B(tài)編程,根本思路都是:〔1〕為每個精靈類編寫Attack、FightBack和Hurted成員函數(shù)?!?〕Attact函數(shù)表現(xiàn)攻擊動作,攻擊某個精靈,并調(diào)用被攻擊精靈的Hurted函數(shù),以減少被攻擊精靈的生命值,同時也調(diào)用被攻擊精靈的FightBack成員函數(shù),遭受被攻擊精靈還擊?!?〕Hurted函數(shù)減少自身生命值,并表現(xiàn)受傷動作〔4〕FightBack成員函數(shù)表現(xiàn)還擊動作,并調(diào)用被還擊對象的Hurted成員函數(shù),使被還擊對象受傷先看非多態(tài)的實現(xiàn)方法:classCSoldier{private: intnPower;/代表攻擊力intnLifeValue;//代表生命值public: intAttack(CKirin*pKirin) { ...//表現(xiàn)攻擊動作的代碼 pKirin->Hurted(nPower); pKirin->FightBack(this); } intAttack(CAngel*pAngel) { ...//表現(xiàn)攻擊動作的代碼 pAngel->Hurted(nPower); pAngel->FightBack(this); } intAttack(CGriffin*pGriffin) { ...//表現(xiàn)攻擊動作的代碼 pGriffin->Hurted(nPower); pGriffin->FightBack(this); }

intFightBack(CKirin*pKirin) { ....//表現(xiàn)還擊動作的代碼 pKirin->Hurted(nPower/2); } intFightBack(CAngel*pAngel) { ....//表現(xiàn)還擊動作的代碼 pAngel->Hurted(nPower/2); } intFightBack(CGriffin*pGriffin) { ....//表現(xiàn)還擊動作的代碼 pGriffin->Hurted(nPower/2); } intHurted(intnPower) { ....//表現(xiàn)受傷動作的代碼 nLifeValue-=nPower; }}如果游戲版本升級,增加了新的精靈半馬人,那么程序改動較大。首先要寫個CCentaur類,它結(jié)構(gòu)和CSoldier類似。另外,所有已有的精靈類的類都需要增加下面兩個成員函數(shù),在精靈種類多的時候,工作量較大。 intAttack(CCentaur*pCentaur); intFightBack(Centaur*pCentaur);下面用多態(tài)的方法來對游戲進行設(shè)計,對所有精靈共同的屬性和方法抽象為一個基類CCreature

基類CCreature為:classCCreature{public: virtualvoidAttack(CCreature*pCreature)=0; virtualintHurted(intnPower)=0; virtualintFightBack(CCreature*pCreature)=0; intnLifeValue,nPower;}派生類CSoldier為:classCSoldier:publicCCreature{public: virtualvoidAttack(CCreature*pCreature) { pCreature->Hurted(nPower); pCreature->FightBack(this); } virtualintHurted(intnPower) { ....//表現(xiàn)受傷動作的代碼 nLifeValue-=nPower; } virtualintFightBack(CCreature*pCreature) { ....//表現(xiàn)還擊動作的代碼 pCreature->Hurted(nPower/2); };}那么當增加新精靈半馬人的時候,只需要編寫新類CCentaur,不需要在已有的類里專門為新精靈增加成員函數(shù):

int

Attack(CCentaur*pCentaur);

int

FightBack(Centaur*pCentaur);具體使用這些類的代碼:CSoldiersoldier;CKirinkirin;CAngelangel;CGriffingriffin;CCentaurcentaur;soldier.Attack(&kirin); //(1)soldier.Attack(&angel); //(2)soldier.Attack(&griffin); //(3)soldier.Attack(¢aur); //(4)例6.34家里養(yǎng)了一些寵物,你還可以在養(yǎng)新的寵物,你能叫出家里所有寵物的名字。先看非多態(tài)的實現(xiàn):classDog{ stringname;public: voidprintname(){cout<<"thisisdog"<<name<<endl;} Dog(strings):name(s){}};classCat{ stringname;public: voidprintname(){cout<<"thisiscat"<<name<<endl;} Cat(strings):name(s){}};classHome{ intnDogs; intnCats; Dog*pDogs[20]; Cat*pCats[20];public: voidAdd(Dog*pDog){pDogs[nDogs++]=pDog;} voidAdd(Cat*pCat){pCats[nCats++]=pCat;}

voidprintAll() { for(inti=0;i<nCats;i++) pCats[i]->printname(); for(i=0;i<nDogs;i++) pDogs[i]->printname(); } Home():nDogs(0),nCats(0){}};intmain(){ Homemyhome; myhome.Add(newDog("dog1")); myhome.Add(newDog("dog2")); myhome.Add(newDog("dog3")); myhome.Add(newCat("cat1")); myhome.Add(newCat("cat2")); myhome.Add(newCat("cat3")); myhome.printAll(); return0;}如果家里又新養(yǎng)了個寵物Rabbit,非多態(tài)方式不便于擴充,添加新寵物時要改寫Home類:classHome{ …

int

nRabbit;public: … Cat*pRabbit[20]; voidAdd(Rabbit*pRabbit){pRabbit[nRabbit++]=pRabbit;} voidprintAll() { …

for(i=0;i<nRabbit;i++)

pRabbit[i]->printname(); } Home():nDogs(0),nCats(0),nRabbit(0){}}多態(tài)實現(xiàn)方法classPet{public: stringname; Pet(strings):name(s){} virtualvoidprintname(){}};classDog:publicPet{public: Dog(stri

溫馨提示

  • 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)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論