第6講 虛基類多態(tài)性和虛函數(shù)new_第1頁
第6講 虛基類多態(tài)性和虛函數(shù)new_第2頁
第6講 虛基類多態(tài)性和虛函數(shù)new_第3頁
第6講 虛基類多態(tài)性和虛函數(shù)new_第4頁
第6講 虛基類多態(tài)性和虛函數(shù)new_第5頁
已閱讀5頁,還剩58頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第六講虛基類、多態(tài)性和虛函數(shù)武漢大學(xué)王泉德1一、虛基類1.單繼承classAclassBclassC2多重繼承和虛基類2.多重繼承classAclassBclassC3多重繼承派生類的定義設(shè)類B是類A1、A2、…、An的派生類,多重繼承的派生類的定義形式為:class<B>:[<派生方式1>]<A1>, [<派生方式2>]<A2>,…, [<派生方式3>]<An>{... //派生類新增加的成員列表

};4例定義一個派生類C,它是類A和B的派生類。classA {protected:

inta;public:voidSetA(int

na);};classB {protected:

intb;public:voidSetB(int

nb);};classC:publicA,publicB

{private:

intc;public:

int

SetAB(int

na,int

nb);};

5多重繼承中的二義性問題classC:publicA

{public:

intc;};classD:publicB,publicC

{public:

intd;};main(){Dd1;d1.a=100;}classA{public:

inta;};classB:publicA{public:

intb;};6多重繼承中的二義性問題classBclassCclassDclassADBACA派生類D的對象中存在間接基類A的兩份副本

7解決方法一利用作用域限定符(::)把基類的成員與下一層基類關(guān)聯(lián)起來:

d1.B::a=100;//d1.C::a=100

缺點:浪費了存儲空間;在訪問基類的成員時,要求指明訪問路徑。大部分情況下不需要保存基類多個相同的副本。8解決方法二(虛基類)虛基類并不是一種新的類型的類,而是一種派生方式。采用虛基類方式定義派生類,在創(chuàng)建派生類的對象時,類層次結(jié)構(gòu)中虛基類的成員只出現(xiàn)一次,即基類的一個副本被所有派生類對象所共享。9虛基類classBclassCclassDclassADBAC10虛基類派生方式的定義采用虛基類方式定義派生類的方法是在基類的前面加上關(guān)鍵字virtual,而定義基類時與一般基類完全一樣。語法如下:class<派生類名>:virtual<派生方式><共同基類名>例采用virtual虛基類方式定義派生類。classB:virtualpublicA{public:

intb;};classC:virtualpublicA{public:

intc;};主函數(shù)中:

d1.a=100;

√11虛基類的初始化虛基類的初始化與一般多繼承的初始化在語法上相同,但構(gòu)造函數(shù)的調(diào)用順序有所不同,規(guī)則如下:先調(diào)用虛基類的構(gòu)造函數(shù),再調(diào)用非虛基類的構(gòu)造函數(shù)。若同一層次包含多個虛基類,其調(diào)用順序為定義時的順序。若虛基類由非虛基類派生而來,則仍按先調(diào)用基類構(gòu)造函數(shù),再調(diào)用派生類構(gòu)造函數(shù)的順序。12引入虛基類后構(gòu)造函數(shù)的調(diào)用順序舉例#include<iostream.h>classBase1{public: Base1(){

cout<<"classBase1";

cout<<endl; }};classBase2{public: Base2(){

cout<<"classBase2";

cout<<endl;} };classLevel1:publicBase2,virtualpublicBase1{public: Level1(){

cout<<"classLevel1";

cout<<endl;}};classLevel2:publicBase2,virtualpublicBase1{public: Level2(){

cout<<"classLevel2";

cout<<endl; }};classTopLevel:publicLevel1,virtualpublicLevel2{public:

TopLevel(){

cout<<"classTopLevel";

cout<<endl; }};voidmain(){

TopLevel

obj;}運行結(jié)果:classBase1classBase2classLevel2classBase2classLevel1classTopLevel13使用虛基類派生方式的好處節(jié)約內(nèi)存空間;避免在多重派生類中類成員的不明確性。14虛基類構(gòu)造函數(shù)的調(diào)用說明建立對象時所指定的類稱為最(遠(yuǎn))派生類。虛基類的成員是由最派生類的構(gòu)造函數(shù)通過調(diào)用虛基類的構(gòu)造函數(shù)進(jìn)行初始化的。在整個繼承結(jié)構(gòu)中,直接或間接繼承虛基類的所有派生類,都必須在構(gòu)造函數(shù)的成員初始化表中給出對虛基類的構(gòu)造函數(shù)的調(diào)用。如果未列出,則表示調(diào)用該虛基類的缺省構(gòu)造函數(shù)。在建立對象時,只有最派生類的構(gòu)造函數(shù)調(diào)用虛基類的構(gòu)造函數(shù),該派生類的其它基類對虛基類構(gòu)造函數(shù)的調(diào)用被忽略。15二、多態(tài)性和虛函數(shù)何謂多態(tài)性?多態(tài)性也是面向?qū)ο蟪绦蛟O(shè)計方法的一個重要特征,它主要表現(xiàn)在函數(shù)調(diào)用時實現(xiàn)“一種接口、多種方法”。16多態(tài)性是指類中同一函數(shù)名對應(yīng)多個具有相似功能的不同函數(shù),可以使用相同的調(diào)用方式來調(diào)用這些具有不同功能的同名函數(shù)的特性。在C++程序中,多態(tài)性表現(xiàn)為同一種調(diào)用方式完成不同的處理。從實現(xiàn)角度來劃分,多態(tài)可分為編譯時多態(tài)和運行時多態(tài)。編譯時多態(tài)是指在編譯階段由編譯系統(tǒng)根據(jù)操作數(shù)據(jù)確定調(diào)用哪個同名函數(shù)。(函數(shù)重載)運行時多態(tài)是指在運行階段才根據(jù)產(chǎn)生的信息確定需要調(diào)用哪個同名的函數(shù)。(虛函數(shù))17兩種多態(tài)性:編譯時多態(tài)性和運行時多態(tài)性編譯時多態(tài)性:在函數(shù)名或運算符相同的情況下,編譯器在編譯階段就能夠根據(jù)函數(shù)參數(shù)類型的不同來確定要調(diào)用的函數(shù)——通過重載實現(xiàn)。運行時多態(tài)性:在函數(shù)名、函數(shù)參數(shù)和返回類型都相同的情況下,只能在程序運行時才能確定要調(diào)用的函數(shù)——通過虛函數(shù)實現(xiàn)。18聯(lián)編多態(tài)性實現(xiàn)過程中,確定調(diào)用哪個同名函數(shù)的過程就是聯(lián)編,又稱綁定。聯(lián)編是指計算機(jī)程序自身彼此關(guān)聯(lián)的過程,也就是將函數(shù)調(diào)用語句與函數(shù)代碼相關(guān)聯(lián)。兩種聯(lián)編方式:靜態(tài)聯(lián)編和動態(tài)聯(lián)編靜態(tài)聯(lián)編是指編譯器在編譯階段就確定了要調(diào)用的函數(shù),即早期綁定。重載采用靜態(tài)聯(lián)編方式。動態(tài)聯(lián)編是指在程序執(zhí)行過程中根據(jù)具體情況再確定要調(diào)用的函數(shù),即后期綁定。當(dāng)通過基類指針調(diào)用虛函數(shù)時,C++采用動態(tài)聯(lián)編方式。虛函數(shù)體現(xiàn)出一種動態(tài)多態(tài)性或運行時多態(tài)性。19靜態(tài)聯(lián)編在編譯階段完成的聯(lián)編稱為靜態(tài)聯(lián)編。在編譯過程中,編譯系統(tǒng)可根據(jù)參數(shù)不同來確定哪一個同名函數(shù)。函數(shù)重載和運算符重載就是通過靜態(tài)聯(lián)編方式實現(xiàn)編譯時多態(tài)性的體現(xiàn)。優(yōu)點:函數(shù)調(diào)用速度快,效率高。缺點:編程不夠靈活。20靜態(tài)聯(lián)編舉例#include<iostream.h>classStudent{public: voidprint(){

cout<<"AStudent"<<endl; }};classGStudent:publicStudent{public: voidprint(){

cout<<"AgraduateStudent<<endl; }};21靜態(tài)聯(lián)編舉例voidmain(){ Students1,*ps;

GStudents2; s1.print(); s2.print(); s2.Student::print();

ps=&s1;

ps->print();

ps=&s2;

ps->print();}運行結(jié)果:AStudentAgraduateStudentAStudent

AStudentAStudent希望調(diào)用對象s2的輸出函數(shù),但是實際調(diào)用的卻是對象s1的輸出函數(shù)。22靜態(tài)聯(lián)編舉例說明:基類指針ps指向派生類對象s2時并沒有調(diào)用派生類的print(),而仍然調(diào)用基類的print(),這是因為靜態(tài)聯(lián)編的結(jié)果。在程序編譯階段,基類指針ps對print()的操作只能綁定到基類的print()上,導(dǎo)致程序輸出了不期望的結(jié)果。而期望的是執(zhí)行派生類的print函數(shù)。23多態(tài)性用法:用基類指針指向派生類對象聲明一個派生類的對象的同時也自動聲明了一個基類的對象。 ——3.3小節(jié)內(nèi)容派生類的對象可以認(rèn)為是其基類的對象。C++允許一個基類對象的指針指向其派生類的對象。

——這是實現(xiàn)虛函數(shù)的關(guān)鍵24注意不允許派生類對象的指針指向其基類的對象。即使將一個基類對象的指針指向其派生類的對象,通過該指針也只能訪問派生類中從基類繼承的公有成員,不能訪問派生類自定義的成員,除非通過強(qiáng)制類型轉(zhuǎn)換將基類指針轉(zhuǎn)換為派生類指針。25基類指針與派生類指針之間的相互轉(zhuǎn)換classA{private:

inta;public:voidSetA(inti) {a=i;};voidShowA(){cout<<a<<endl;};};classB:publicA{private:

intb;public:voidSetB(inti) {b=i;};voidShowB(){cout<<b<<endl;};};26基類指針與派生類指針之間的相互轉(zhuǎn)換voidmain(){Aa,*pa; //pa為基類對象的指針

Bb,*pb; //pb為派生類對象的指針

pa=&b; //基類指針pa指向派生類對象b//通過基類指針pa訪問B中從基類A繼承的公有成員

pa->SetA(100); pa->ShowA();

pb=(B*)pa; //將基類指針轉(zhuǎn)化為派生類指針

pb->SetB(200);

pb->ShowB();}pb=&apa->SetB() pa->ShowB()

27虛函數(shù)classA{public:voidShow(){cout<<"A::Show";};};voidmain(){A*pa;Bb;pa=&b; pa->Show();}classB:publicA{public:voidShow(){cout<<"B::Show";};};

調(diào)用哪一個Show()?如果想通過基類指針調(diào)用派生類中覆蓋的成員函數(shù),只有使用虛函數(shù)。

28虛函數(shù)的聲明虛函數(shù)是在某一個基類中聲明為virtual,并在一個或多個派生類中被重新定義的成員函數(shù)。要將一個成員函數(shù)聲明為虛函數(shù),只需在定義基類時在成員函數(shù)聲明的開始位置加上關(guān)鍵字virtual聲明虛函數(shù)的格式:virtual<返回值類型><函數(shù)名>(<參數(shù)表>);29虛函數(shù)的聲明虛函數(shù)描述類層次體系中相似的行為,是非靜態(tài)的成員函數(shù),經(jīng)過派生之后,虛函數(shù)在類族中可以實現(xiàn)運行時多態(tài)。一個函數(shù)一旦被聲明為虛函數(shù),則無論聲明它的類被繼承了多少層,在每一層派生類中該函數(shù)都保持虛函數(shù)特性。因此在派生類中重新定義該函數(shù)時,可省略關(guān)鍵字virtual。但是為了提高程序的可讀性,往往不省略。30使用虛函數(shù)的結(jié)果classA{public:

virtualvoidShow(){cout<<"A::show\n";};};classB:publicA{public:voidShow(){cout<<"B::show\n";};};voidmain()

{

Aa,*pa;

Bb;

pa=&a;pa->Show();

//調(diào)用函數(shù)A::Show()

pa=&b;pa->Show();

//調(diào)用函數(shù)B::Show()

}程序運行結(jié)果:

A::ShowB::Show31虛函數(shù)實現(xiàn)同一個接口來處理不同的對象CShapeCEllipseCCircleCTriangleCRectangleCSquare32voidmain(){

CShape

aShape;

CEllipse

aEllipse;

CCircle

aCircle;

CTriangle

aTriangle;

CRectangle

aRect;

CSquare

aSquare;

CShape*pShape[6]={&aShape, &aEllipse, &aCircle, &aTriangle, &aRect, &aSquare,};

for(inti=0;i<6;i++){

pShape[i]->Draw(); }}33對象在內(nèi)存里的存放方式classA{private:

int

m_na;public: voidSetA(inta) {

m_na=a; }};Aa1;a1.SetA(5);Aa2;a2.SetA(10);A1.SetA和a2.SetA調(diào)用的是用一個地址的函數(shù)。它是通過this來取得各自的成員變量。m_na對象a1m_na對象a2A::SetA(inta,A*this){

this->m_na=a;}thisthis34虛函數(shù)的實現(xiàn)-通過虛函數(shù)地址表classA{public:

inta;virtualvoidvFunc1();virtualvoidvFunc2();voidshow();};vptra(*vFunc1)()(*vFunc2)()classA::vFunc1()classA::vFunc2()classA::show()A的對象35虛函數(shù)的實現(xiàn)-通過虛函數(shù)地址表classB:publicA{public:

intb;

virtualvoidvFunc2();voidshow();};vptra(*vFunc1)()(*vFunc2)()classA::vFunc1()classB::vFunc2()classB::show()A的對象b36虛函數(shù)使用實例:圖形繪制RectTriShape在圖形類層次體系中有如下行為RotateDrawArea希望通過指向基類Shape對象的指針統(tǒng)一完成繪制不同圖形的工作Circle37虛函數(shù)使用實例:圖形繪制classShape{public: doublet;

int

ntype; doubles; Circle*pCir; Tri*pTri;

CRect*pRect;public: voidarea(); voidarea(Shape*,int);};classCircle{private: doubler;doubles;public:

Circle(int

a){r=a;} staticint

ntype; voidarea(){s=PI*r*r;cout<<"theareaofciris:"<<s<<endl;}};int

Circle::ntype=0;38虛函數(shù)使用實例:圖形繪制classCRect{private: doublewi,hi; doubles; doublexl,yl;public: staticint

ntype;

CRect(double

w,double

h){wi=w;hi=h;} voidarea(){ s=hi*wi;

cout<<"theareaofciris:"<<s<<endl; } };int

CRect::ntype=1;39虛函數(shù)使用實例:圖形繪制classTri{private: doublea,b,c;doubles; public: staticint

ntype;

Tri(double

ai,doublebi,doubleci){a=ai;b=bi;c=ci;} voidarea(){ doublep=(a+b+c)/2; s=sqrt(p*(p-a)*(p-b)*(p-c));

cout<<"theareaofciris:"<<s<<endl; }};int

Tri::ntype=2;40虛函數(shù)使用實例:圖形繪制voidShape::area(Shape*pShape,int

ntypeobj){

ntype=ntypeobj;

switch(ntype){ case0://circle

pCir=(Circle*)pShape; pCir->area();break; case1:

pRect=(CRect*)pShape;pRect->area();break; case2:

pTri=(Tri*)pShape;pTri->area(); break; default: break; }}這種處理方式中,area函數(shù)必須理解現(xiàn)存的所有形狀,當(dāng)有新的類加入系統(tǒng),則處理形狀的所有操作都必須修改。如果無法接觸新類的代碼,則無法實現(xiàn)新類的繪制。41虛函數(shù)使用實例:圖形繪制intmain(){ Tritriobj(3,4,5);

CRectrectobj(4,5); Circlecirobj(5); Shape*pshobj; Shapeshobj;

pshobj=(Shape*)&rectobj;

shobj.area(pshobj,rectobj.ntype); return0;}//運行結(jié)果:Theareaofcircleis:20基于多態(tài)和虛函數(shù),我們可以使得程序大為簡化。42虛函數(shù)使用實例:圖形繪制classShape{pointcenter; colorcol;virtualvoiddraw(); virtualvoidrotate(); virtualvoidarea(); };intmain(){

CRectrectobj(4,5); Circlecirobj(5); Shape*pshobj;

pshobj=&rectobj;

shobj->area(); return0;}//首先定義一個類shape描述所有形狀的普遍性質(zhì),,在這里只給出調(diào)用接口,不給出具體實現(xiàn)。具體實現(xiàn)在特定的具體類中給出.43虛函數(shù)使用實例:圖形繪制voidAreaAll(shape*v,intsize){

for(intI=0;i<size;i++)

v[i]->Draw();};有了這個定義,可以給出一個對各種形狀進(jìn)行求面積的通用函數(shù)。44總結(jié)利用虛函數(shù)可以在基類和派生類中使用相同的函數(shù)名和參數(shù)類型,但定義不同的操作。這樣,就為同一個類體系中所有派生類的同一類行為(其實現(xiàn)方法可以不同)提供了一個統(tǒng)一的接口。

例如,在一個圖形類繼承結(jié)構(gòu)中,設(shè)類CShape是所有具體圖形類(如矩形、三角形或圓等)的基類,則函數(shù)調(diào)用語句“pShape->Draw()”可能是繪制矩形,也可能是繪制三角形或圓。具體繪制什么圖形,取決于pShape所指的對象。45構(gòu)造函數(shù)與虛函數(shù)基于構(gòu)造函數(shù)的特點,不能將構(gòu)造函數(shù)定義為虛函數(shù)。按照c++的標(biāo)準(zhǔn)語法,構(gòu)造函數(shù)不能是虛函數(shù),因為構(gòu)造函數(shù)是不會通過指針調(diào)用的,虛函數(shù)沒有意義。46構(gòu)造函數(shù)與虛函數(shù)的結(jié)論構(gòu)造函數(shù)不用設(shè)置為虛函數(shù)。47析構(gòu)函數(shù)與虛函數(shù)當(dāng)撤消派生類的對象時,先調(diào)用派生類析構(gòu)函數(shù),然后自動調(diào)用基類析構(gòu)函數(shù),如此看來析構(gòu)函數(shù)沒必要定義為虛函數(shù)。但是,假如使用基類指針指向其派生類的對象,而這個派生類對象是用new運算創(chuàng)建的。當(dāng)程序使用delete運算撤消派生類對象時,這時只調(diào)用了基類的析構(gòu)函數(shù),而沒有調(diào)用派生類的析構(gòu)函數(shù)。48析構(gòu)函數(shù)與虛函數(shù)的結(jié)論析構(gòu)函數(shù)視情況而定,可設(shè)置為虛函數(shù)。用類向?qū)砑拥念?,析?gòu)函數(shù)默認(rèn)是虛函數(shù)。49虛析構(gòu)函數(shù)的使用classA{public://構(gòu)造函數(shù)不能是虛函數(shù) A(){};

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

virtual~A() {cout<<“A::析構(gòu)\n";};};classB:publicA{public: B(){};

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

~B() {cout<<“B::析構(gòu)\n";}; };voidmain(){ A*pA=newB;

//...

deletepA;

//先調(diào)用派生類B的析構(gòu)函數(shù),再調(diào)用基類A的析構(gòu)函數(shù)}程序運行結(jié)果:

B::析構(gòu)

A::析構(gòu)50虛析構(gòu)函數(shù)的使用classA{public: A(){};//構(gòu)造函數(shù)不能是虛函數(shù)

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

virtual~A(){cout<<“A::析構(gòu)\n";};};classB:publicA{public: B(){};

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

~B(){cout<<“B::析構(gòu)\n";}; };如果析構(gòu)函數(shù)不是虛函數(shù),則得不到剛才的運行結(jié)果。請思考會是什么結(jié)果

總結(jié):由于使用了虛析構(gòu)函數(shù),當(dāng)撤消pA所指派生類B的對象時,首先調(diào)用派生類B的析構(gòu)函數(shù),然后再調(diào)用基類A的析構(gòu)函數(shù)。51虛析構(gòu)函數(shù)的使用一般來說,可將類族中的具有共性的成員函數(shù)聲明為虛函數(shù),而具有個性的函數(shù)則沒有必要聲明為虛函數(shù)。但是以下情況例外:靜態(tài)成員函數(shù)不能聲明為虛函數(shù)。因為靜態(tài)函數(shù)不屬于某一個對象,沒有多態(tài)性的特征。內(nèi)聯(lián)函數(shù)不能聲明為虛函數(shù)。因為內(nèi)聯(lián)函數(shù)的執(zhí)行代碼明確,沒有虛函數(shù)的特征。構(gòu)造函數(shù)不能聲明為虛函數(shù)。析構(gòu)函數(shù)可以是虛函數(shù)。52有些類是抽象的CShape是抽象的,它根本不該有Draw()的動作。但為了統(tǒng)一接口,又必須定義它。雖然可以讓它為空,但卻不是一個高明的做法。因為它根本就不應(yīng)該被調(diào)用。所以有下面的…53抽象類和純虛函數(shù)何謂抽象類?抽象類是類的一些行為(成員函數(shù))沒有給出具體定義的類,即純粹的一種抽象。抽象類只能用于類的繼承,其本身不能用來創(chuàng)建對象,抽象類又稱為抽象基類。54抽象類和純虛函數(shù)抽象基類只提供了一個框架,僅僅起著一個統(tǒng)一接口的作 用,而很多具體的功能由派生出來的類去實現(xiàn)。雖然不能聲明抽象類的對象,但可以聲明指向抽象類的指針。

在一般的類庫中都使用了抽象基類,如類CObject就是微軟基礎(chǔ)類庫MFC的抽象基類。55什么樣的類為抽象基類?一個類如果滿足以下兩個條件之

溫馨提示

  • 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

提交評論