《C++程序設(shè)計(jì)教程》課件第7章_第1頁(yè)
《C++程序設(shè)計(jì)教程》課件第7章_第2頁(yè)
《C++程序設(shè)計(jì)教程》課件第7章_第3頁(yè)
《C++程序設(shè)計(jì)教程》課件第7章_第4頁(yè)
《C++程序設(shè)計(jì)教程》課件第7章_第5頁(yè)
已閱讀5頁(yè),還剩163頁(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)介

7.1概念

7.2多態(tài)

7.3虛函數(shù)的說(shuō)明7.4分解與抽象7.5多繼承 7.6虛擬繼承

7.7對(duì)象的構(gòu)造本章要點(diǎn)

練習(xí)

7.1.1靜態(tài)聯(lián)編

開(kāi)發(fā)一個(gè)應(yīng)用程序一般總要完成從編輯到編譯和連接,最后生成可執(zhí)行程序。在編譯時(shí)編譯器就將程序中各模塊、各函數(shù)之間的調(diào)用關(guān)系確定下來(lái)并生成目標(biāo)文件,這種方式稱為靜態(tài)聯(lián)編(staticbinding)。

在下面的例子中,基類(lèi)Student中有一個(gè)Display函數(shù),在派生類(lèi)GraduateStudent中也定義了一個(gè)和基類(lèi)相同的函數(shù)Display,然后通過(guò)各自的對(duì)象或指針,調(diào)用自己的成員函數(shù)。7.1概

念【例7-1】演示靜態(tài)聯(lián)編。

#include<iostream>

#include<string>

usingnamespacestd;

classStudent

{private:

stringname;charsex;

protected:

intnum;public:

Student(intn=982,stringnam="Wanggang",chars='M')

{name=nam;num=n;sex=s;}

voidDisplay()

{cout<<"num:"<<num<<'\t'<<"name:"<<name<<'\t'<<"sex:"<<sex<<endl;}

};

classGraduateStudent:publicStudent{private:

intage;stringaddr;

public:

GraduateStudent(intn,stringnam,chars,intag,stringad):Student(n,nam,s)

{age=ag;addr=ad;}

voidDisplay(){cout<<"age:"<<age<<'\t'<<"address:"<<addr<<endl;}

};voidmain()

{Studenta,*s;GraduateStudentb(994,"Lolee",'W',26,"shanghai"),*g;

s=&a;g=&b;

s->Display(); //調(diào)用基類(lèi)的成員函數(shù)

g->Display(); //調(diào)用派生類(lèi)的成員函數(shù)

}運(yùn)行結(jié)果:

num:982name:wanggangsex:M

age:26address:shanghai

在上面的程序中,各自對(duì)象的指針都調(diào)用自己的成員函數(shù)。若用對(duì)象名引導(dǎo)也能得到同樣的輸出結(jié)果。分析程序可見(jiàn),在派生類(lèi)GraduateStudent中重定義了Display函數(shù),g->Display()調(diào)用了派生類(lèi)的Display函數(shù)。在上面的調(diào)用關(guān)系中,無(wú)論是調(diào)用派生類(lèi),還是調(diào)用基類(lèi)的Display,編譯器在編譯時(shí)就已經(jīng)確定下來(lái)了,它們是靜態(tài)聯(lián)編。那么用基類(lèi)指針指向派生類(lèi)對(duì)象,然后調(diào)用Display,輸出結(jié)果將如何呢?下面我們將主函數(shù)修改如下:

voidmain()

{Studenta;

GraduateStudentb(994,“Lolee”,‘W’,26,“shanghai”);

Student*s;

s=&b;

s->Display(); //調(diào)用基類(lèi)的成員函數(shù)

}運(yùn)行結(jié)果:

num:994name:Loleesex:W

從運(yùn)行結(jié)果可知,將基類(lèi)指針指向派生類(lèi)對(duì)象后,還是調(diào)用基類(lèi)成員函數(shù)Display,訪問(wèn)派生類(lèi)中由基類(lèi)繼承下來(lái)的成員。雖然在派生類(lèi)中重定義了Display函數(shù),但是用該方法還是只能訪問(wèn)基類(lèi)的Display函數(shù),原因是s是基類(lèi)指針。

我們希望當(dāng)基類(lèi)指針指向派生類(lèi)后,若派生類(lèi)中重定義了基類(lèi)的成員函數(shù),則調(diào)用該重定義函數(shù),否則調(diào)用基類(lèi)的成員函數(shù)。即同樣的一句調(diào)用語(yǔ)句,調(diào)用效果不同。7.1.2動(dòng)態(tài)聯(lián)編

在繼承關(guān)系中,基類(lèi)中的某成員函數(shù)在派生類(lèi)中實(shí)現(xiàn)了重定義。若用基類(lèi)指針指向派生類(lèi)對(duì)象,然后通過(guò)該指針調(diào)用成員函數(shù),則調(diào)用的還是基類(lèi)成員函數(shù)。若能調(diào)用派生類(lèi)中重定義的函數(shù),并在重定義函數(shù)中賦予新的功能,不就相當(dāng)于改變了原基類(lèi)成員函數(shù)的功能嗎?

通過(guò)基類(lèi)指針(或引用)請(qǐng)求使用虛函數(shù)時(shí),C++會(huì)在與對(duì)象關(guān)聯(lián)的派生類(lèi)中正確選擇重定義的函數(shù),這稱為動(dòng)態(tài)聯(lián)編(dynamicbinding)或多態(tài)。7.1.3虛函數(shù)

虛函數(shù)就是將某個(gè)基類(lèi)的成員函數(shù)標(biāo)記為virtual的成員函數(shù)。為了指明某個(gè)成員函數(shù)具有多態(tài),用關(guān)鍵字virtual來(lái)標(biāo)志其為虛函數(shù)。標(biāo)志方法是:若函數(shù)既有聲明也有實(shí)現(xiàn),則在函數(shù)聲明前加上關(guān)鍵字virtual,則該普通成員函數(shù)就成了虛函數(shù),在函數(shù)實(shí)現(xiàn)部分不需要再加virtual;若在類(lèi)聲明中定義虛函數(shù),則在該函數(shù)前加上關(guān)鍵字virtual。7.2.1多態(tài)的兩種方式

在面向?qū)ο蟪绦蛟O(shè)計(jì)中,多態(tài)有兩種形式:編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)。運(yùn)行時(shí)多態(tài)是面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言的一大特點(diǎn)。

編譯時(shí)多態(tài)是指在編譯時(shí)調(diào)用哪個(gè)函數(shù)是確定的。本章中的例7-1程序,包括改動(dòng)后的main函數(shù),雖然用基類(lèi)指針指向派生類(lèi)對(duì)象,或者將該指針強(qiáng)制轉(zhuǎn)換為派生類(lèi)的指針,但所調(diào)用的成員函數(shù)在編譯時(shí)就能確定,即為靜態(tài)聯(lián)編。

運(yùn)行時(shí)多態(tài)是指在運(yùn)行時(shí)才能確定調(diào)用哪個(gè)函數(shù),即為動(dòng)態(tài)聯(lián)編,或稱為動(dòng)態(tài)綁定(dynamicbinding)。嚴(yán)格地講,運(yùn)行時(shí)多態(tài)才是真正意義上的多態(tài)。7.2多態(tài)7.2.2多態(tài)的實(shí)現(xiàn)

多態(tài)是在繼承關(guān)系中通過(guò)虛函數(shù)和基類(lèi)指針來(lái)實(shí)現(xiàn)的。具體步驟為:先將基類(lèi)中的一些成員函數(shù)聲明為虛函數(shù),然后在它的派生類(lèi)中又對(duì)該虛函數(shù)重定義,通過(guò)基類(lèi)指針(或引用)調(diào)用虛函數(shù)時(shí),標(biāo)準(zhǔn)操作功能實(shí)現(xiàn)的流程就會(huì)調(diào)用派生類(lèi)(即當(dāng)前應(yīng)用類(lèi))的虛函數(shù)。

簡(jiǎn)單地說(shuō),實(shí)現(xiàn)多態(tài)要分兩步:第一步,先標(biāo)明要想實(shí)現(xiàn)多態(tài)的虛函數(shù);第二步,用基類(lèi)指針指向派生類(lèi)對(duì)象。

【例7-2】該例提供基類(lèi)Person和它的兩個(gè)派生類(lèi)Teacher和Student,說(shuō)明如何通過(guò)虛函數(shù)和基類(lèi)指針實(shí)現(xiàn)多態(tài)。person.h //person.h頭文件

#ifndefPERSON_H

#definePERSON_H

classPerson //有虛函數(shù)的類(lèi)稱為多態(tài)類(lèi)

{protected:

charname[10];charsex;intnum;

public:

Person(intn=982,charnam[]=“LeHua”,chars=‘M’);

virtualvoidDisplay();

//若不加virtual,則兩次都調(diào)用該函數(shù)

virtual~Person(){}

//有一個(gè)虛函數(shù),就提供了一個(gè)虛析構(gòu)函數(shù)

};

#endifperson.cpp //person.cpp實(shí)現(xiàn)文件

#include<iostream>

usingnamespacestd;

#include"person.h"

Person::Person(intn,charnam[],chars)

{strcpy(name,nam);num=n;sex=s;}voidPerson::Display() //虛函數(shù)實(shí)現(xiàn)部分不能再加virtual

{cout<<“DisplayPerson”<<endl;

cout<<“num\t”<<“name\t”<<“sex\t”<<endl;

cout<<num<<“\t”<<name<<“\t”<<sex<<“\n”<<endl;

}

student.h //student.h頭文件

#ifndefSTUDENT_H

#defineSTUDENT_H

#include“person.h”

//定義派生類(lèi)時(shí)要包含基類(lèi)

classStudent:publicPerson{public:

Student(intn,charnam[],chars,intx);

virtualvoidDisplay();

private:

intscore;

};

#endifstudent.cpp //student.cpp實(shí)現(xiàn)文件

#include<iostream>

usingnamespacestd;

#include"student.h"

Student::Student(intn,charnam[],chars,intx):Person(n,nam,s)

{score=x;}voidStudent::Display() //函數(shù)的實(shí)現(xiàn)中不需要virtual

{cout<<"DisplayStudent"<<endl;

cout<<"num\t"<<"name\t"<<"sex\t"<<"score"<<endl;

cout<<num<<'\t'<<name<<'\t'<<sex<<'\t'<<score<<endl;

}teacher.h //teacher.h頭文件

#ifndefTEACHER_H

#defineTEACHER_H

#include"person.h" //定義派生類(lèi)時(shí)要包含基類(lèi)

classTeacher:publicPerson{public:

Teacher(intn,charnam[],chars,intx);

virtualvoidDisplay(); //在派生類(lèi)中virtual可省略

private:

intarticle;

};

#endifteacher.cpp //teacher.cpp實(shí)現(xiàn)文件

#include<iostream>

usingnamespacestd;

#include"teacher.h"

Teacher::Teacher(intn,charnam[],chars,intx)

:Person(n,nam,s)

{article=x;}

voidTeacher::Display() //虛函數(shù)實(shí)現(xiàn)部分不加virtual

{cout<<"DisplayTeacher"<<endl;

cout<<"num\t"<<"name\t"<<"sex\t"<<"article"<<endl;

cout<<num<<'\t'<<name<<'\t'<<sex<<'\t'<<article<<endl;

}expA.cpp //應(yīng)用程序

#include"teacher.h" //包含頭文件

#include"student.h" //包含頭文件

voidmain()

{Persond,*p; //基類(lèi)對(duì)象和指針

Teachera(201,"Randy",'M',3);

Studentb(202,"Jonas",'W',85);d.Display(); //對(duì)象名和分量運(yùn)算符調(diào)用函數(shù),靜態(tài)聯(lián)編

a.Display(); b.Display();

p=&d; //基類(lèi)指針指向基類(lèi)對(duì)象d

p->Display(); //用指針p和指針運(yùn)算符來(lái)訪問(wèn),動(dòng)態(tài)聯(lián)編

p=&a; //基類(lèi)指針指向派生類(lèi)對(duì)象a

p->Display(); //動(dòng)態(tài)聯(lián)編,調(diào)用Teacher::Display()

p=&b; //基類(lèi)指針指向派生類(lèi)對(duì)象b

p->Display(); //動(dòng)態(tài)聯(lián)編,調(diào)用Student::Display()

}運(yùn)行結(jié)果:(多態(tài))

DisplayPerson //調(diào)用Person::Display()輸出

num name sex

982 LeHua

M

DisplayTeacher //調(diào)用Teacher::Display()輸出

num name sex article

201 Randy

M

3

DisplayStudent //調(diào)用Student::Display()輸出

num name sex score

202 Jonas W 857.2.3多態(tài)的工作方法

編譯通常是在靜態(tài)聯(lián)編下進(jìn)行的,當(dāng)遇到虛函數(shù)后,編譯器就在類(lèi)的每個(gè)對(duì)象內(nèi)存儲(chǔ)一些信息,指明在程序運(yùn)行中應(yīng)該匹配哪個(gè)函數(shù)。即將一些數(shù)據(jù)和執(zhí)行指令存儲(chǔ)起來(lái),待以后在程序的動(dòng)態(tài)執(zhí)行中再完成動(dòng)態(tài)聯(lián)編,確定調(diào)用哪個(gè)函數(shù)。

在C++中的實(shí)現(xiàn)技術(shù)是為每個(gè)帶有虛函數(shù)的類(lèi)建立一個(gè)“虛函數(shù)表”,編譯器把一個(gè)virtual函數(shù)名轉(zhuǎn)換為指向這些函數(shù)的“虛函數(shù)表”的下標(biāo)。虛函數(shù)表簡(jiǎn)稱vtbl,也就是指針表,表中的每一個(gè)指針都指向一個(gè)虛函數(shù)。當(dāng)發(fā)生函數(shù)調(diào)用時(shí),指針指向哪個(gè)對(duì)象,就給出了虛函數(shù)表中的下標(biāo),于是就匹配到了正確調(diào)用的虛函數(shù)。簡(jiǎn)單地說(shuō),虛函數(shù)的調(diào)用如同一次間接函數(shù)調(diào)用。7.2.4多態(tài)的需要性

一個(gè)基類(lèi)可以有許多派生類(lèi),若用強(qiáng)制轉(zhuǎn)換的方法,程序員必須一個(gè)一個(gè)指針地進(jìn)行強(qiáng)制轉(zhuǎn)換。再者派生類(lèi)又可以作為新的基類(lèi),再進(jìn)行派生,又要由程序員來(lái)將一個(gè)一個(gè)指針進(jìn)行轉(zhuǎn)換。即程序員必須將各指針對(duì)號(hào)入座。這就如同又回到了面向過(guò)程的方式中,而且程序維護(hù)的工作很重。假如不強(qiáng)制轉(zhuǎn)換,而是定義每一個(gè)派生類(lèi)的指針,雖也能實(shí)現(xiàn)調(diào)用派生類(lèi)中重定義函數(shù)的目的,但可以設(shè)想一下,這樣要定義多少指針。有了多態(tài),只要有一個(gè)基類(lèi)的指針,再將它指向某對(duì)象,不論它是直接派生類(lèi)還是間接派生類(lèi),余下的任務(wù)就由對(duì)象來(lái)表現(xiàn)。多態(tài)給我們帶來(lái)了方便和可讀性,所以說(shuō)實(shí)現(xiàn)多態(tài)是面向?qū)ο蟪绦蛟O(shè)計(jì)的關(guān)鍵。

多態(tài)是動(dòng)態(tài)生成的,它既要求為每個(gè)對(duì)象存儲(chǔ)某種類(lèi)型信息,又要求實(shí)現(xiàn)動(dòng)態(tài)匹配。所以動(dòng)態(tài)聯(lián)編會(huì)降低程序的執(zhí)行速度,同時(shí)也要占據(jù)一定的內(nèi)存。

1.多態(tài)與重定義的區(qū)別

若將例7-2基類(lèi)中的virtual關(guān)鍵字去掉,對(duì)于同樣的expA.cpp應(yīng)用程序,輸出結(jié)果如下:

DisplayPerson //調(diào)用Person::Display()輸出

num name sex

982 LeHua

M

DisplayPerson //調(diào)用Person::Display()輸出

num name sex201 Randy

M

DisplayPerson //調(diào)用Person::Display()輸出

num name sex

202 Jonas W

輸出此結(jié)果的原因是,在執(zhí)行p->Display();時(shí),由于p是基類(lèi)指針,不論它指向基類(lèi)對(duì)象還是派生類(lèi)對(duì)象,只要不是虛函數(shù),它只能調(diào)用基類(lèi)中的成員函數(shù)。

2.多態(tài)應(yīng)用舉例

為使我們對(duì)多態(tài)有更好的了解,現(xiàn)通過(guò)兩個(gè)例子來(lái)分析和介紹兩種提高多態(tài)可讀性的方法。方法一通過(guò)指針,方法二通過(guò)引用。

【例7-3】演示實(shí)現(xiàn)多態(tài)的方法一,在例7-2中添加一個(gè)SHow函數(shù),程序如下:

expC.cpp

//應(yīng)用程序

#include"teacher.h"

#include"student.h"

voidSHow(Person*r) //該處要用基類(lèi)的指針

{r->Display();} //動(dòng)態(tài)聯(lián)編voidmain()

{Persond;

Teachera(201,"Randy",'M',3);

Studentb(202,"Jonas",'W',85);

SHow(&d); //調(diào)用基類(lèi)的Display

SHow(&a); //調(diào)用派生類(lèi)的Display

SHow(&b); //調(diào)用派生類(lèi)的Display

}運(yùn)行結(jié)果:與expA.cpp相同(多態(tài))。

在派生類(lèi)和基類(lèi)中都定義了Display(),若同樣地調(diào)用SHow(&a)、SHow(&b),則分別調(diào)用各自的Display()。若派生類(lèi)中沒(méi)有定義Display(),則調(diào)用基類(lèi)中的Display()。

【例7-4】演示實(shí)現(xiàn)多態(tài)的方法二,對(duì)例7-2中的expA.cpp作改動(dòng),說(shuō)明如何通過(guò)基類(lèi)的引用實(shí)現(xiàn)多態(tài)。添加一個(gè)Show函數(shù)如下:

expD.cpp

//應(yīng)用程序

#include"teacher.h"

#include"student.h"

voidShow(Person&x) //通過(guò)引用實(shí)現(xiàn)多態(tài)

{x.Display();} //動(dòng)態(tài)聯(lián)編voidmain()

{Persond;

Teachera(201,"Randy",'M',3);

Studentb(202,"Jonas",'W',85);

Show(d); //調(diào)用基類(lèi)的Display

Show(a); //調(diào)用派生類(lèi)的Display

Show(b); //調(diào)用派生類(lèi)的Display

}

運(yùn)行結(jié)果:與expA.cpp相同(多態(tài))。增加了一個(gè)中間函數(shù),可將基類(lèi)的指針也省掉。我們用Show(d)、Show(a)和Show(b)可以分別調(diào)用對(duì)象d、a和b中的成員函數(shù)Display。函數(shù)調(diào)用的表示方式是一致的、獨(dú)立的,而函數(shù)功能可以不同。假如我們想要顯示,不管它是文字還是圖表,我們關(guān)心的只是顯示,可通過(guò)調(diào)用顯示函數(shù)Show,由該函數(shù)自己選擇匹配函數(shù)。所以說(shuō),多態(tài)增加了程序的可讀性、可維護(hù)性和可擴(kuò)充性。

多態(tài)的實(shí)現(xiàn),使我們只要關(guān)心顯示這一工作,并不需要關(guān)心調(diào)用哪一個(gè)Display()函數(shù),使我們考慮問(wèn)題大為簡(jiǎn)單化。多態(tài)使應(yīng)用程序代碼大大簡(jiǎn)化,它是開(kāi)啟繼承能力的鑰匙。7.2.5關(guān)于多態(tài)的說(shuō)明

1.多態(tài)要通過(guò)虛函數(shù)和指針來(lái)實(shí)現(xiàn)

要在C++中表現(xiàn)出多態(tài)的行為,被調(diào)函數(shù)必須是虛函數(shù),而對(duì)象則必須是通過(guò)指針或者引用去操作。若通過(guò)對(duì)象名加分量運(yùn)算符來(lái)調(diào)用虛函數(shù),則被調(diào)用的虛函數(shù)是在編譯時(shí)就能確定的,是靜態(tài)聯(lián)編。

2.虛函數(shù)的聲明內(nèi)容必須與基類(lèi)一致

派生類(lèi)中虛函數(shù)的聲明內(nèi)容必須與基類(lèi)中的一致。多態(tài)是通過(guò)虛函數(shù)實(shí)現(xiàn)的,虛函數(shù)必須與基類(lèi)中的函數(shù)具有相同的名字、相同的參數(shù)和返回類(lèi)型,并在前面再加上virtual。若僅僅是名字相同,而參數(shù)類(lèi)型不同,或返回類(lèi)型不同,即使寫(xiě)上關(guān)鍵字virtual,C++也不視其為虛函數(shù),也不能進(jìn)行動(dòng)態(tài)聯(lián)編。

3.建議提供一個(gè)虛析構(gòu)函數(shù)

若類(lèi)有一個(gè)虛函數(shù),就應(yīng)具有一個(gè)虛析構(gòu)函數(shù)。例如:virtual~Person(){…}或virtual~Person();Person::~Person(){…}。

4.可以將基類(lèi)函數(shù)設(shè)置為虛函數(shù)

在一個(gè)類(lèi)中將所有的成員函數(shù)都盡可能地設(shè)置為虛函數(shù),也是可以的。但請(qǐng)注意,使虛函數(shù)工作的內(nèi)部機(jī)制需要占據(jù)大量的內(nèi)存。通常使用消息處理機(jī)來(lái)避免使用大量的虛

函數(shù)。

5.抑制虛機(jī)制

在派生類(lèi)中若要訪問(wèn)基類(lèi)的虛函數(shù)可以使用類(lèi)名加域區(qū)分符來(lái)實(shí)現(xiàn),這時(shí)我們稱它為虛函數(shù)的靜態(tài)調(diào)用,稱為抑制虛機(jī)制。例如有Person*p;Teachera;p=&a;,則p->Person::Display();調(diào)用的是基類(lèi)Person類(lèi)的Display()。7.3.1虛函數(shù)的限制

1.虛函數(shù)必須是成員函數(shù)

只有成員函數(shù)才能聲明為虛函數(shù),這是因?yàn)樘摵瘮?shù)僅適用于有繼承關(guān)系類(lèi)的對(duì)象,所以普通函數(shù)不能聲明為虛函數(shù)。

2.靜態(tài)成員函數(shù)不能是虛函數(shù)

靜態(tài)成員函數(shù)不能是虛函數(shù),因?yàn)殪o態(tài)成員函數(shù)屬于類(lèi),不屬于某個(gè)對(duì)象。7.3虛函數(shù)的說(shuō)明

3.內(nèi)聯(lián)函數(shù)不能是虛函數(shù)

內(nèi)聯(lián)函數(shù)不能是虛函數(shù),因?yàn)閮?nèi)聯(lián)函數(shù)不能在運(yùn)行中動(dòng)態(tài)確定位置。即使虛函數(shù)在類(lèi)的內(nèi)部定義,編譯時(shí)仍將它看做非內(nèi)聯(lián)函數(shù)。

4.構(gòu)造函數(shù)不可以是虛函數(shù)

構(gòu)造函數(shù)不可以是虛函數(shù),因?yàn)闃?gòu)造時(shí)對(duì)象還是一片未定型的空間,只有在構(gòu)造完成后,對(duì)象才能成為一個(gè)類(lèi)的名副其實(shí)的實(shí)例。

5.析構(gòu)函數(shù)可以是虛函數(shù)

析構(gòu)函數(shù)可以是虛函數(shù)。定義一個(gè)虛析構(gòu)函數(shù),可以在多態(tài)情況下正確調(diào)用析構(gòu)函數(shù)。7.3.2虛函數(shù)與重載函數(shù)的區(qū)別

通俗地講,重載函數(shù)是具有相同名稱、不同參數(shù)的函數(shù),而本質(zhì)上是不同的函數(shù),而且代碼具體執(zhí)行哪一個(gè)函數(shù)在程序編譯階段已確定。虛函數(shù)則是在程序執(zhí)行階段根據(jù)派生類(lèi)的虛函數(shù)重定義情況進(jìn)行選擇執(zhí)行的,這就為派生類(lèi)中改變已經(jīng)存在的函數(shù)代碼提供了實(shí)現(xiàn)方法。函數(shù)重載必須具有不同的原型,而用于覆蓋的虛函數(shù)必須具有相同的原型。重載的成員函數(shù)能夠具有幾個(gè)名稱相同、功能類(lèi)似,但參數(shù)不同的函數(shù)。而派生類(lèi)中的虛成員函數(shù)與基類(lèi)同名、同參和同類(lèi)型,它代替了基類(lèi)中定義的函數(shù)版本,它具有不同的行為。

虛函數(shù)只能在繼承關(guān)系中發(fā)揮作用。函數(shù)重載不能用于繼承關(guān)系中,它只在同一作用域內(nèi)有效。在繼承關(guān)系中可以用函數(shù)的重定義,但它是靜態(tài)聯(lián)編。7.4.1分解方法

下面我們通過(guò)描寫(xiě)父親類(lèi)和母親類(lèi)來(lái)掌握抽象與分解的方法。若有當(dāng)教師的父親和做會(huì)計(jì)的母親兩個(gè)類(lèi),我們可以用如圖7-1所示的方法描述。7.4分?解?與?抽?象圖7-1類(lèi)的描述從上面的兩個(gè)類(lèi)中可以發(fā)現(xiàn),兩個(gè)類(lèi)有許多共同的成員,可以通過(guò)繼承使問(wèn)題簡(jiǎn)化。由于Mother有額外的數(shù)據(jù)成員,因此讓它繼承Father比較合理。Mother繼承了Father之后我們只要在Mother類(lèi)中增加Account()、KeepFit()和Deposit()便可。由于父母在學(xué)習(xí)方式和內(nèi)容上可能有所不同,因此將成員函數(shù)Study()設(shè)為虛函數(shù)來(lái)完成。若以后由于職業(yè)的變動(dòng),學(xué)習(xí)方式和內(nèi)容發(fā)生了改變,則只要在派生類(lèi)中重定義虛函數(shù)便可。但是我們可以再思考一下,Mother繼承了Father,Mother也有teachbook,這對(duì)她來(lái)說(shuō)沒(méi)用,而且增加了混淆;再說(shuō)母親繼承父親也不合常理。解決方法是再進(jìn)行分解與抽象。

Father和Mother的共性是長(zhǎng)輩,是雙親,于是可以抽象出Parent類(lèi),將Parent類(lèi)再分解出Farther和Mother。也就是說(shuō),將Father和Mother類(lèi)都作為Parent的派生類(lèi)。

在圖7-2中,F(xiàn)ather類(lèi)和Mother類(lèi)的成員函數(shù)Study()為虛函數(shù)。Father類(lèi)繼承了Parent類(lèi),自身補(bǔ)充了teachbook數(shù)據(jù)成員。Mother類(lèi)繼承了Parent類(lèi),自身補(bǔ)充了AccountNo和equipment數(shù)據(jù)成員以及Study()、Account()、KeepFit()、Deposit()成員函數(shù)。圖7-2類(lèi)繼承關(guān)系分解時(shí)要使類(lèi)的層次合理化,減少冗余,同時(shí)還要考慮到類(lèi)的可擴(kuò)展性。分解時(shí)要注意,只有當(dāng)繼承關(guān)系與實(shí)際相符合時(shí),分解才是合理的。

從上面的分解結(jié)果可以看出,雙親是一個(gè)概念,是不可能有實(shí)體的,現(xiàn)實(shí)生活中并不存在雙親的一個(gè)對(duì)象。由于不存在對(duì)象,那么何來(lái)研究、學(xué)習(xí)呢?在Parent類(lèi)中,Study()成員函數(shù)實(shí)際上沒(méi)什么可干,它似乎沒(méi)有意義,但又是必需的,因?yàn)樗且粋€(gè)虛函數(shù),如果沒(méi)有該函數(shù),就無(wú)法實(shí)現(xiàn)多態(tài)。

繼承和多態(tài)的組合,可以輕易地生成一系列好像類(lèi)似,但卻是獨(dú)一無(wú)二的對(duì)象。由于繼承性,這些對(duì)象共享了許多相似的特征。由于多態(tài),這些對(duì)象的每一個(gè)都可以有獨(dú)特的表現(xiàn)方式。7.4.2抽象類(lèi)

有了分解就可以抽象,抽象的目的是歸類(lèi)。對(duì)類(lèi)進(jìn)行抽象,就是提取事物的共性,它是分解的逆過(guò)程。由抽象而組成的類(lèi),稱為抽象類(lèi)。

1.抽象類(lèi)的特點(diǎn)

C++允許程序員聲明一個(gè)不能有實(shí)例對(duì)象的類(lèi),這樣的類(lèi)唯一的用途是被繼承。C++中規(guī)定,一個(gè)抽象類(lèi)有且至少有一個(gè)純虛函數(shù)。所謂純虛函數(shù),是指被標(biāo)明為不具體實(shí)現(xiàn)的虛成員函數(shù)。因此也就可以說(shuō),具有純虛函數(shù)的類(lèi)就是抽象類(lèi)。如下面代碼中的virtualvoidStudy()=0;,首先Study是虛函數(shù),其次它等于0,即標(biāo)明它為不具體實(shí)現(xiàn)的虛函數(shù),于是Study就成了純虛函數(shù)。

//family.h

#ifndefPARENT

#definePARENT

classParent

{protected:

intorgan;floatmoney;intbrain;public:

voidTour();

voidHousehold();

virtualvoidWatch()=0; //純虛函數(shù)在聲明時(shí)初始化為0

virtualvoidStudy()=0;

virtual~Parent(){}

};

#endif

在上面的例子中,Parent類(lèi)用virtualvoidStudy()=0;聲明了一個(gè)純虛函數(shù)Study()。該聲明是為派生類(lèi)保留位置的,即在派生類(lèi)中被期待用具體函數(shù)來(lái)重定義而實(shí)現(xiàn)某種功能。因此Parent類(lèi)就是一個(gè)抽象類(lèi)。

2.抽象類(lèi)的作用

抽象類(lèi)是作為基類(lèi)為其他類(lèi)服務(wù)的,它是由其他各類(lèi)的共性歸集成的類(lèi)。它本身并不希望聲明對(duì)象使用,不能由抽象類(lèi)來(lái)創(chuàng)建實(shí)例對(duì)象。抽象類(lèi)的唯一作用是被繼承。在派生類(lèi)中才賦予基類(lèi)中純虛函數(shù)的實(shí)現(xiàn)功能。抽象類(lèi)的使用可以為我們提供一個(gè)界面,而不暴露任何實(shí)現(xiàn)細(xì)節(jié)。抽象類(lèi)為類(lèi)層次結(jié)構(gòu)中的各個(gè)成員函數(shù)定義接口。抽象類(lèi)中包含要在派生類(lèi)中重定義的純虛函數(shù),該層次結(jié)構(gòu)中的所有相同的函數(shù)都可以通過(guò)多態(tài)使用同樣的接口。

盡管不能實(shí)例化抽象基類(lèi)的對(duì)象,但卻可以聲明抽象基類(lèi)的指針。當(dāng)實(shí)例化了具體類(lèi)的對(duì)象后,可以用這種指針使派生類(lèi)對(duì)象具有多態(tài)操作的能力。

在一個(gè)抽象類(lèi)的派生類(lèi)中,要對(duì)所有基類(lèi)中的純虛函數(shù)重定義,否則該派生類(lèi)會(huì)自動(dòng)成為抽象類(lèi),而不能定義對(duì)象。若用抽象類(lèi)定義了對(duì)象,程序連接時(shí)會(huì)出錯(cuò)。

3.抽象類(lèi)應(yīng)用

這里有一個(gè)例子是實(shí)現(xiàn)工資單的計(jì)算。領(lǐng)工資者和工資性質(zhì)是:經(jīng)理類(lèi),領(lǐng)固定年薪;銷(xiāo)售員類(lèi),基本工資+銷(xiāo)售額×系數(shù);計(jì)時(shí)工類(lèi),計(jì)時(shí)工資+加班費(fèi)。

為了實(shí)現(xiàn)題意要求,首先要進(jìn)行抽象,找出以上三個(gè)類(lèi)所共有的行為與特征。由于經(jīng)理、銷(xiāo)售員和計(jì)時(shí)工都是員工,因此抽象出一個(gè)員工類(lèi)作為虛基類(lèi),將三個(gè)類(lèi)的共性歸入其中,以備代碼重用。沒(méi)有歸入某一類(lèi)的單純的員工是不存在的,這樣的抽象符合實(shí)際。然后再根據(jù)各類(lèi)員工的特性分解出三個(gè)類(lèi),都作為員工類(lèi)的派生類(lèi)。類(lèi)的派生關(guān)系如圖7-3所示。圖7-3類(lèi)的派生關(guān)系在實(shí)現(xiàn)中,用基類(lèi)指針ep指向派生類(lèi)對(duì)象,然后通過(guò)ep->Print();調(diào)用ep所指向?qū)ο蟮某蓡T函數(shù)Print。由于Print在基類(lèi)中被聲明為虛函數(shù),因此調(diào)用了派生類(lèi)對(duì)象的Print函數(shù)。用基類(lèi)指針調(diào)用與對(duì)象關(guān)聯(lián)的派生類(lèi)的成員函數(shù),可以在執(zhí)行時(shí)才確定調(diào)用哪個(gè)函數(shù)(多態(tài)行為)。

【例7-5】計(jì)算員工工資。

employee.h //頭文件

#ifndefEMPLOY_H

#defineEMPLOY_H#include<iostream>

#include<string>

usingnamespacestd;

classEmployee

//抽象類(lèi),員工類(lèi)

{public:

Employee(string);

virtualfloatCount()const=0; //純虛函數(shù)

virtualvoidPrint()const=0; //純虛函數(shù)

virtual~Employee(){}

protected:

stringname;

};

#endifemployee.cpp //實(shí)現(xiàn)文件

#include<string>

usingnamespacestd;

#include"employee.h"

Employee::Employee(strings){name=s;}

director.h //頭文件

#ifndefDIRECT_H

#defineDIRECT_H

#include"employee.h"classDirector:publicEmployee

//經(jīng)理類(lèi)

{private:

floatYear_Salary;

public:

Director(string,float=0.0);

//float=0.0,帶默認(rèn)參數(shù)的聲明法

voidSetYearSalary(float);

virtualfloatCount()const;

virtualvoidPrint()const;

};

#endifdirector.cpp //實(shí)現(xiàn)文件

#include“director.h”

Director::Director(stringname,floats):Employee(name)

{Year_Salary=s>0?s:0;}

voidDirector::SetYearSalary(floats){Year_Salary=s>0?s:0;}

floatDirector::Count()const

//派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{returnYear_Salary;}voidDirector::Print()const //派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{cout<<“\nDirector:”<<name;}

commis.h

//頭文件

#ifndefCOMMIS1_H

#defineCOMMIS1_H

#include“employee.h”

classSalesman:publicEmployee

//銷(xiāo)售員類(lèi){private:

floatsalary;floatsale;intquantity;

public:

Salesman(string,float=0.0,float=0.0,int=0);

voidSetSalary(float,float,int);

virtualfloatCount()const;

virtualvoidPrint()const;

};

#endif;

commis.cpp //實(shí)現(xiàn)文件

#include"commis.h"Salesman::Salesman(stringname,floats,floatc,intq):Employee(name)

{salary=s>0?s:0;sale=c>0?c:0;quantity=q>0?q:0;}

voidSalesman::SetSalary(floats,floatc,intq)

{salary=s>0?s:0;sale=c>0?c:0;quantity=q>0?q:0;}

floatSalesman::Count()const //派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{returnsalary+sale*quantity;}

voidSalesman::Print()const //派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{cout<<"\nSalesman:"<<name;}hourly.h

//頭文件

#ifndefHOURLY1_H

#defineHOURLY1_H

#include“employee.h”

classHourlyWorker:publicEmployee

//計(jì)時(shí)工類(lèi)

{private:

floathourly;floathours;

public:

HourlyWorker(string,float=0.0,float=0.0);

voidSetHoursWage(float,float);

virtualfloatCount()const;

virtualvoidPrint()const;

};

#endifhourly.cpp //實(shí)現(xiàn)文件

#include"hourly.h"

HourlyWorker::HourlyWorker(stringname,floatw,floath):Employee(name)

{hourly=w>0?w:0;

hours=h>=0&&h<168?h:0;

}

voidHourlyWorker::SetHoursWage(floatw,floath)

{hourly=w>0?w:0;

hours=h>=0&&h<168?h:0;

}floatHourlyWorker::Count()const

//派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{returnhourly*hours;}

voidHourlyWorker::Print()const

//派生類(lèi)重定義基類(lèi)的純虛函數(shù)

{cout<<"\nHourlyWorker:"<<name;}main.cpp //實(shí)現(xiàn)文件

#include<iomanip>

#include"employee.h"

#include"director.h"

#include"commis.h"

#include"hourly.h"

voidFun(Employee&p) //動(dòng)態(tài)聯(lián)編

{p.Print();

cout<<"\tearned\t$"<<p.Count();

}voidFun(Employee*p) //動(dòng)態(tài)聯(lián)編

{p->Print();

cout<<“\tearned\t$”<<p->Count();

}

voidmain()

{cout<<setiosflags(ios::fixed|ios::showpoint)<<setprecision(2);

Employee*ep;

//基類(lèi)指針,它是一個(gè)虛基類(lèi)指針

Directorb("Bleriot");b.SetYearSalary(800.00); //靜態(tài)聯(lián)編

ep=&b;

//基類(lèi)指針指向派聲類(lèi)對(duì)象

ep->Print(); //動(dòng)態(tài)聯(lián)編

cout<<"\tearned\t$"<<ep->Count(); //動(dòng)態(tài)聯(lián)編

Salesmanc("Johnny",200.0,3.0,150);

Fun(c); //通過(guò)引用實(shí)現(xiàn)多態(tài)

HourlyWorkerh("Karen",13.75,40);

Fun(h); //動(dòng)態(tài)聯(lián)編

cout<<endl;

}運(yùn)行結(jié)果:

Director:Bleriot earned$800.00

Salesmanr:Johnny earned$650.00

HourlyWorker:Karen earned$550.00

【例7-6】編寫(xiě)程序,求基本圖形的面積。對(duì)于基本圖形,即三角形、矩形和圓等,有一個(gè)共同的任務(wù)——求面積,但是求面積的方式不同。通過(guò)此分析就可以找出一個(gè)基類(lèi)——圖形類(lèi),將求面積作為它的純虛函數(shù),在它的派生類(lèi)中再具體實(shí)現(xiàn)各個(gè)求法。從求面積的方法中我們又發(fā)現(xiàn),三角形面積為(底×高)/2;矩形面積為長(zhǎng)×寬;圓面積為π×R2。其共性都是要求有兩個(gè)數(shù),然后再做運(yùn)算,因此可將這兩個(gè)數(shù)歸入基類(lèi)中。類(lèi)的派生關(guān)系如圖7-4所示。 圖7-4類(lèi)的派生關(guān)系#include<iostream>

usingnamespacestd;

constdoublePI=3.14159;

classFigure //基本圖形類(lèi),抽象類(lèi)

{protected:

doublex_size;doubley_size;

public:

voidset_size(double=0,double=0);

virtualdoubleget_area()=0;

//聲明get_area()為純虛函數(shù)

virtual~Figure(){}

};voidFigure::set_size(doublex,doubley) //成員函數(shù)

{x_size=x;y_size=y;}

classTrangle:publicFigure

//三角形類(lèi)

{public:

doubleget_area()

//求三角形面積

{return(x_size*y_size/2);}

};classRectangle:publicFigure //矩形類(lèi)

{public:

doubleget_area()

{return(x_size*y_size);} //求矩形面積

};

classCircle:publicFigure //圓類(lèi)

{public:

doubleget_area()

{return(PI*x_size*x_size);}//求圓形面積

};intmain()

{Figure*figure; //定義基類(lèi)指針

Trangletrangle; //定義三角形類(lèi)對(duì)象

Rectanglerectangle; //定義矩形類(lèi)對(duì)象

Circlecircle; //定義圓類(lèi)對(duì)象

figure=&trangle;

//基類(lèi)指針指向派生類(lèi)三角形類(lèi)的對(duì)象

figure->set_size(15,8); //設(shè)置三角形的底和高

cout<<"Areaoftrangleis"

<<figure->get_area()<<endl; //輸出三角形面積,動(dòng)態(tài)聯(lián)編figure=&rectangle;

figure->set_size(15,8); //設(shè)置矩形的長(zhǎng)和寬

cout<<"Areaofrectangleis"

<<figure->get_area()<<endl; //輸出矩形面積,動(dòng)態(tài)聯(lián)編

figure=&circle;

figure->set_size(15); //設(shè)置圓半徑

cout<<"Areaofcircleis"

<<figure->get_area()<<endl; //輸出圓面積,動(dòng)態(tài)聯(lián)編

return0;

}在例7-6中有一個(gè)基類(lèi)和三個(gè)派生類(lèi)。通過(guò)虛函數(shù),用一個(gè)基類(lèi)指針指向它的派生類(lèi),該指針就會(huì)在與對(duì)象關(guān)聯(lián)的派生類(lèi)中正確選擇重定義的函數(shù),實(shí)現(xiàn)多態(tài)。這樣就可以不改變基類(lèi)中的標(biāo)準(zhǔn)代碼而使函數(shù)實(shí)現(xiàn)不同的功能。

【例7-7】通過(guò)本例體會(huì)抽象和分類(lèi)、繼承關(guān)系的實(shí)現(xiàn)以及構(gòu)造函數(shù)的激活方式。

圖7-5中,將圖形分為二維圖形和三維圖形。二維圖形中有圓、矩形和三角形等,三維圖形中有球和立方體等。實(shí)現(xiàn)方法:添加FigureTwo和FigureThree類(lèi),然后在二維圖形中派生出圓等,在三維圖形中派生出球等。圖7-5類(lèi)的層次結(jié)構(gòu)#include<iostream>

usingnamespacestd;

constdoublePI=3.14159;

classFigure //基本圖形類(lèi),抽象類(lèi)

{protected:

doublex_size;doubley_size;

public:

Figure(double=0,double=0);

//新增基類(lèi)構(gòu)造函數(shù)

voidset_size(doublex,doubley=0);

virtualdoubleget_area()=0;

//純虛函數(shù)

virtual~Figure(){}

};Figure::Figure(doublex,doubley)

{x_size=x;y_size=y;}

voidFigure::set_size(doublex,doubley)

{x_size=x;y_size=y;}

classFigureTwo:publicFigure

//二維圖形,自動(dòng)成為抽象類(lèi)

{public:

FigureTwo(doublex=0,doubley=0)

:Figure(x,y) //激活基類(lèi)構(gòu)造函數(shù)

{}

};classTrangle:publicFigureTwo //三角形類(lèi)

{public:

virtualdoubleget_area() //求三角形面積

{return(x_size*y_size/2);}

};

classRectangle:publicFigureTwo //矩形類(lèi)

{public:

virtualdoubleget_area()

{return(x_size*y_size);} //求矩形面積

};classCircle:publicFigureTwo //圓類(lèi)

{public:

virtualdoubleget_area()

{return(PI*x_size*x_size);} //求圓面積

};

classFigureThree:publicFigure //以下代碼處理三維圖形

{public:

FigureThree(doublex=0,doubley=0)

:Figure(x,y) //激活基類(lèi)構(gòu)造函數(shù)

{}

virtualdoubleget_area(){return3;}

};classGlobular:publicFigureThree

{public:

virtualdoubleget_area() //求球體積

{return(PI*x_size*x_size*x_size*4/3);}

};

classCube:publicFigureThree

{protected:

doublez_size;

public:

Cube::Cube(doublex,doubley,doublez)

:FigureThree(x,y) //激活基類(lèi)構(gòu)造函數(shù)

{z_size=z;}

virtualdoubleget_area()

{return(x_size*y_size*z_size);} //求立方體體積

};voidmain()

{Figure*figure;Globularglobular; //定義基類(lèi)指針

figure=&globular; //基類(lèi)指針指向派生類(lèi)對(duì)象

figure->set_size(3);

cout<<“Volumeofglobularis”<<figure->get_area()<<endl;

Cubecube(2,3,4);

figure=&cube;

cout<<“Volumeofcubeis”<<figure->get_area()<<endl;

}

從例7-7中Cube類(lèi)的構(gòu)造函數(shù)的激活方式中可見(jiàn),各個(gè)類(lèi)的構(gòu)造函數(shù)只能激活它的直接基類(lèi)構(gòu)造函數(shù),而不能激活間接基類(lèi)構(gòu)造函數(shù)。多繼承(multipleinheritance)有兩種情況:一種是一個(gè)類(lèi)同時(shí)被多個(gè)類(lèi)繼承,如圖7-6所示;另一種是一個(gè)派生類(lèi)同時(shí)繼承多個(gè)基類(lèi),如圖7-7所示。

第一種情況沒(méi)有什么特殊要求,嚴(yán)格地說(shuō)它不能稱為多繼承,而只能稱為共享基類(lèi)。在此只討論第二種情況。7.5多繼承圖7-7多繼承7.5.1多繼承的概念

有了繼承就可以通過(guò)重用現(xiàn)有的類(lèi)來(lái)構(gòu)建新類(lèi),實(shí)現(xiàn)代碼的可重用性,既方便編程又提高可讀性。既然這樣,我們自然想到,能否同時(shí)用多個(gè)類(lèi)來(lái)構(gòu)建新類(lèi),將多個(gè)類(lèi)的行為和特征都?xì)w為新類(lèi)所有。

C++中允許一個(gè)類(lèi)同時(shí)繼承兩個(gè)或兩個(gè)以上的類(lèi),即一個(gè)類(lèi)是多個(gè)基類(lèi)的派生類(lèi)。這種關(guān)系稱為多繼承。

多繼承是一個(gè)類(lèi)從多個(gè)基類(lèi)派生而來(lái)的能力。派生類(lèi)獲取了所有基類(lèi)的特性。C++支持多繼承,從而大大加強(qiáng)了面向?qū)ο蟪绦蛟O(shè)計(jì)的能力。7.5.2繼承關(guān)系的限制

在C++中允許有直接基類(lèi)與間接基類(lèi),那么就有可能形成一個(gè)環(huán)。C++中不允許一個(gè)類(lèi)直接或間接繼承自己,如圖7-8所示。

重復(fù)繼承(repeatedinheritance)是指一個(gè)派生類(lèi)多次重復(fù)繼承同一個(gè)基類(lèi)。若某程序支持多繼承,那么重復(fù)繼承問(wèn)題是不可避免的。在C++中對(duì)重復(fù)繼承作了部分限制,不允許直接重復(fù)繼承,即不允許一個(gè)派生類(lèi)直接繼承同一基類(lèi)兩次,如圖7-9所示。圖7-8循環(huán)繼承圖7-9重復(fù)繼承直接基類(lèi)

C++不允許一個(gè)基類(lèi)既是直接基類(lèi)又是間接基類(lèi),如圖7-10所示。但是,C++對(duì)于繼承同一間接基類(lèi)并沒(méi)有限制,如圖7-11所示。有重復(fù)繼承,就有可能出現(xiàn)數(shù)據(jù)成員的冗余或名字沖突。在C++中提供了一些策略解決重復(fù)繼承中的問(wèn)題。圖7-10直接和間接基類(lèi)圖7-11重復(fù)繼承間接基類(lèi)7.5.3實(shí)現(xiàn)方法

如圖7-12所示,兩用車(chē)同時(shí)繼承了客車(chē)和卡車(chē)兩個(gè)類(lèi)。

在實(shí)現(xiàn)中要在派生類(lèi)的冒號(hào)之后的派生表中列出所有基類(lèi)的類(lèi)名,各基類(lèi)之間用逗號(hào)隔開(kāi)。例如有classBusLorry:publicBus,publicLorry,則BusLorry類(lèi)同時(shí)公有繼承了Bus和Lorry類(lèi)。圖7-12多繼承派生類(lèi)的構(gòu)造函數(shù)必須激活所有基類(lèi)的構(gòu)造函數(shù),方法是在派生類(lèi)的構(gòu)造函數(shù)后加冒號(hào)和基類(lèi)構(gòu)造函數(shù),如BusLorry(intx,inty):Bus(x),Lorry(y)。若不給出基類(lèi)構(gòu)造函數(shù),則此刻實(shí)際上是激活默認(rèn)構(gòu)造函數(shù)。在多繼承下的類(lèi)域中,所有直接基類(lèi)被同時(shí)查找,如果兩個(gè)或多個(gè)基類(lèi)繼承了同名的成員,就會(huì)出現(xiàn)名字沖突。7.5.4繼承中的二義性

在多繼承中可能會(huì)發(fā)生成員函數(shù)的名字沖突。例如,在下面給出的例子中,兩用車(chē)?yán)^承了客車(chē)和卡車(chē)。在客車(chē)和卡車(chē)中都有Display,那么在它們的派生類(lèi)兩用車(chē)中就不能區(qū)分Display的歸屬。兩個(gè)Display名字沖突,出現(xiàn)二義性。解決方法之一是用類(lèi)名引導(dǎo),進(jìn)行顯式調(diào)用。通過(guò)在函數(shù)名前加上類(lèi)名的調(diào)用方法稱為顯式調(diào)用,如例7-8中的bl.Bus::Display();。還有一種解決方法是在兩用車(chē)中進(jìn)行重定義。

【例7-8】解決二義性的方法一,用類(lèi)名引導(dǎo)。

#include<iostream>

usingnamespacestd;

classBus

{protected:

intweight;

public:

Bus(intx){weight=x;}

voidTake(){cout<<"Takeing\n";}

voidDisplay(){cout<<"Bus:"<<weight<<endl;}

};classLorry

{protected:

intweight;

public:

Lorry(intx){weight=x;}

voidCarriage(){cout<<"Transport...\n";}

voidDisplay(){cout<<"Lorry:"<<weight<<endl;}

};classBusLorry:publicBus,publicLorry

{public:

BusLorry(intx,inty):Bus(x),Lorry(y) //激活基類(lèi)

{}

};

voidmain()

{BusLorrybl(25,13);

//bl.Display(); //出現(xiàn)二義性

bl.Bus::Display(); //顯式調(diào)用Bus的Display

bl.Lorry::Display(); //顯式調(diào)用Lorry的Display

}在本例中用添加類(lèi)名引導(dǎo)的方法,有效地區(qū)分了兩個(gè)Display。顯式調(diào)用的主要目的是解決二義性。若沒(méi)有出現(xiàn)二義性,從語(yǔ)法的角度看也能用此方法,但顯然是多余的。

成員函數(shù)的二義性也可以通過(guò)重定義解決。如可以在BusLorry中,對(duì)Display進(jìn)行重定義來(lái)解決二義性。在繼承關(guān)系中除了成員函數(shù)有二義性之外,數(shù)據(jù)成員有時(shí)會(huì)出現(xiàn)模

糊性。7.5.5繼承中的模糊性

我們以兩用車(chē)為例。在車(chē)輛中,有了卡車(chē)和客車(chē),現(xiàn)在想描述兩用車(chē)。兩用就是可乘坐可運(yùn)輸,所以兩用車(chē)同時(shí)具備卡車(chē)和客車(chē)的功能,我們可以讓兩用車(chē)同時(shí)繼承卡車(chē)和客車(chē)。兩用車(chē)是有重量的,但它繼承哪一個(gè)重量,客車(chē)還是卡車(chē)?這使得對(duì)重量的設(shè)定變得模糊不清。假如有:BusLorrybl;bl.SetWeight();,則被調(diào)用的函數(shù)不知道是Bus的SetWeight(),還是Lorry的SetWeight(),編譯時(shí)將出錯(cuò),解決方法是進(jìn)行分解。

從現(xiàn)實(shí)意義中可知,兩用車(chē)沒(méi)有客車(chē)和卡車(chē)兩種重量。由于客車(chē)和卡車(chē)都是車(chē)輛的一種,車(chē)輛是有重量的,因此通過(guò)分解,讓客車(chē)和卡車(chē)都繼承車(chē)輛,兩用車(chē)再繼承客車(chē)和卡車(chē),如圖7-13所示。圖7-13多繼承關(guān)系由于是多繼承,此時(shí)又出現(xiàn)了問(wèn)題??蛙?chē)?yán)^承車(chē)輛后得到車(chē)輛的一份拷貝,如圖7-14所示。卡車(chē)?yán)^承車(chē)輛后也得到車(chē)輛的一份拷貝,如圖7-15所示。接著兩用車(chē)再繼承客車(chē)和卡車(chē)后又得到客車(chē)和卡車(chē)的一個(gè)拷貝,如圖7-16所示,這樣兩用車(chē)就有了兩個(gè)汽車(chē)的重量,與實(shí)際情況不吻合。體現(xiàn)在編程中,就發(fā)生了成員的冗余。下一節(jié)中將介紹如何通過(guò)虛擬繼承的方法來(lái)解決該問(wèn)題。圖7-14客車(chē)的內(nèi)存分配圖7-15卡車(chē)的內(nèi)存分配圖7-16兩用車(chē)的內(nèi)存分配7.6.1實(shí)現(xiàn)方法

指定虛基類(lèi)的方法是,在有繼承關(guān)系的基類(lèi)繼承訪問(wèn)控制前加上關(guān)鍵字virtual。具有虛基類(lèi)的繼承關(guān)系我們稱為虛擬繼承。

如汽車(chē)類(lèi)虛擬繼承車(chē)輛類(lèi),表述為:classBus:virtualpublicVehicle,它的意思是,不管Vehicle類(lèi)被繼承多少次,在Vehicle類(lèi)的派生類(lèi)對(duì)象中,只要一份Vehicle類(lèi)數(shù)據(jù)成員的拷貝。7.6虛擬繼承在派生類(lèi)對(duì)象中,Vehicle類(lèi)數(shù)據(jù)成員為共享,在與之關(guān)聯(lián)的所有派生類(lèi)對(duì)象中,只要有一個(gè)類(lèi)的成員函數(shù)使該數(shù)據(jù)成員發(fā)生改變,都會(huì)作用到所有派生類(lèi)的同一數(shù)據(jù)成員上。

【例7-9】演示虛擬繼承的方法。#include<iostream>

usingnamespacestd;

classVehicle

{protected:

intweight;

public:

Vehicle(){weight=0;}

voidSetWeight(inti){weight=i;}

intGetWeight(){returnweight;}

};classBus:virtualpublicVehicle //Bus類(lèi)的虛基類(lèi)是Vehicle

{public:

Bus(){number=8;}

voidTake(){cout<<"Takeing...\n";}

protected:

intnumber;

};

classLorry:virtualpublicVehicle //Lorry類(lèi)的虛基類(lèi)是Vehicle

{public:

Lorry(){tonnage=5;}

voidCarriage(){cout<<"Transport…\n";}

protected:

inttonnage;

};classBusLorry:publicBus,publicLorry

{public:

BusLorry():Lorry(),Bus(){}

voidTravel(){cout<<"TakeandTransport.\n";}

};

voidmain()

{BusLorrybl;

bl.SetWeight(44); //兩用車(chē)的重量

cout<<bl.GetWeight()<<endl;

cout<<bl.Bus::GetWeight()<<endl;

cout<<bl.Lorry::GetWeight()<<endl;

cout<<bl.Vehicle::GetWeight()<<endl;

}運(yùn)行結(jié)果:

44

44

44

44

當(dāng)派生類(lèi)的構(gòu)造函數(shù)被激活時(shí),所有基類(lèi)的構(gòu)造函數(shù)也被激活。本例中使用的都是默認(rèn)構(gòu)造函數(shù),因此在程序中我們僅設(shè)定了兩用車(chē)的重量,接著輸出兩用車(chē)中客車(chē)、卡車(chē)和車(chē)輛的重量,它們是相同的,是共享的一個(gè)重量。它們的空間分配如圖7-17所示。BusLorry類(lèi)是Bus類(lèi)、Lorry類(lèi)和Vehicle的直接或間接派生類(lèi)。在BusLorry類(lèi)中有它們的一份

數(shù)據(jù)成員的拷貝。但是,在BusLorry類(lèi)中只有一個(gè)共享的weight,這就是虛擬繼承的結(jié)果。圖7-17空間分配7.6.2激活虛基類(lèi)

虛擬繼承解決了數(shù)據(jù)冗余,冗余是在一個(gè)類(lèi)重復(fù)繼承某一基類(lèi)時(shí)表現(xiàn)出來(lái)的。若基類(lèi)中沒(méi)有默認(rèn)構(gòu)造函數(shù),該如何進(jìn)行參數(shù)傳遞呢?

【例7-10】演示激活虛基類(lèi)的方法。

#include<iostream>

usingnamespacestd;

classVehicle{protected:

intweight;

public:

Vehicle(intx){weight=x;}

};

classBus:virtualpublicVehicle //Bus類(lèi)的虛基類(lèi)是Vehicle

{public:

Bus(intx,inty):Vehicle(y)

{number=x;}

protected:

intnumber;

};classLorry:virtualpublicVehicle

//Lorry類(lèi)的虛基類(lèi)是Vehicle

{public:

Lorry(intx,inty):Vehicle(y)

{tonnage=x;}

protected:

inttonnage;

};classBusLorry:publicBus,publicLorry

{public:

BusLorry(inta,intb,intx):Lorry(a,b),Bus(a,b),Vehicle(x)

{}

voidDisplay()

{cout<<weight<<""<<number<<""<<tonnage<<endl;}

};

voidmain()

{BusLorryb(11,22,33);

b.Display();

}運(yùn)行結(jié)果:

331111

對(duì)于一般派生類(lèi),不需要為間接基類(lèi)提供構(gòu)造函數(shù),因?yàn)椴辉试S在派生類(lèi)的構(gòu)造函數(shù)中初始化間接基類(lèi)。但是對(duì)于虛基類(lèi),就需要傳遞參數(shù),于是就要提供構(gòu)造函數(shù),激活直接基類(lèi)和間接基類(lèi)。如上面的程序中使用了Lorry(a,b)、Bus(a,b)、Vehicle(x),就將參數(shù)直接傳給了直接基類(lèi)和間接基類(lèi)。7.6.3概念區(qū)分

1.虛基類(lèi)與一般類(lèi)

單獨(dú)的一個(gè)類(lèi)是不能成為虛基類(lèi)的,虛基類(lèi)只在繼承關(guān)系中被指定。它不是類(lèi)自身的行為,而是在繼承時(shí)由它的派生類(lèi)來(lái)決定它是否為虛基類(lèi)。如在例7-9中,對(duì)于Vehicle類(lèi),只能說(shuō)它是一般類(lèi),而在Bus類(lèi)中用了classBus:virtualpublicVehicle,才能說(shuō)Vehicle類(lèi)是Bus類(lèi)的虛基類(lèi),Vehicle類(lèi)本身還是一般類(lèi)。若有classCar:publicVehicle,則可以說(shuō)Vehicle類(lèi)是Car類(lèi)的基類(lèi)。

2.繼承與虛擬繼承

繼承與虛擬繼承之間的區(qū)別只有在派生類(lèi)中重復(fù)繼承了被標(biāo)有關(guān)鍵字virtual的基類(lèi),發(fā)生了數(shù)據(jù)成員冗余時(shí)才表現(xiàn)出來(lái),否則并沒(méi)有什么區(qū)別。如在例7-10中,我們將Vehicle定義為虛基類(lèi),對(duì)于Bus與Lorry并無(wú)什么區(qū)別,而當(dāng)它們的派生類(lèi)BusLorry重復(fù)繼承Vehicle時(shí)才表現(xiàn)出來(lái),通過(guò)虛擬繼承只生成一個(gè)weight的副本。

3.虛擬繼承的作用域

從標(biāo)出某類(lèi)為虛基類(lèi)的說(shuō)明點(diǎn)開(kāi)始,在該虛基類(lèi)的派生鏈中虛擬繼承都有效。這也就是說(shuō),虛擬繼承只要標(biāo)出一次,那么在以后它的派生鏈中就無(wú)需再標(biāo)出。如在例7-9中,對(duì)于BusLorry類(lèi)來(lái)說(shuō),Vehicle類(lèi)仍舊是虛基類(lèi)(間接虛基類(lèi))。

4.虛函數(shù)和虛擬繼承

虛函數(shù)和虛擬繼承沒(méi)有任何關(guān)系。虛函數(shù)是為實(shí)現(xiàn)多態(tài)而引入的概念,而虛擬繼承只是為解決在重復(fù)繼承中的數(shù)據(jù)冗余而引入的一種方法。7.6.4虛函數(shù)的二義性

對(duì)于虛基類(lèi)中的純虛函數(shù),在重復(fù)繼承中,也同樣會(huì)出現(xiàn)二義性。因?yàn)樵谂缮?lèi)中要對(duì)純虛函數(shù)重定義,當(dāng)某一類(lèi)同時(shí)繼承了兩個(gè)這樣的派生類(lèi)時(shí),就會(huì)出現(xiàn)函數(shù)的二義性。在下面的例子中,由于在Bus和Lorry兩個(gè)類(lèi)中都對(duì)虛函數(shù)Move()進(jìn)行了重定義,因此通過(guò)BusLorry對(duì)象的調(diào)用就不能確定是Bus::Move還是Lorry::Move。解決的方法是,必須對(duì)重復(fù)繼承中所有基類(lèi)的虛函數(shù)重新定義,以求覆蓋。【例7-11】演示對(duì)虛函數(shù)的重定義。

#include<iostream>

usingnamespacestd;

classVehicle

{public:

virtualvoidMove()=0;

virtual~Vehicle(){}

voidTake(){cout<<"Vehicle"<<endl;}

};classBus:virtualpublicVehicle

//Bus類(lèi)的虛基類(lèi)是Vehicle

{public:

voidMove(){cout<<“Bus\n”;}

};

classLorry:virtualpublicVehicle

//Lorry類(lèi)的虛基類(lèi)是Vehicle

{public:

voidMove(){cout<<"Lorry\n";}

};

classBusLorry:publicBus,publicLorry

{public:

voidTravel(){cout<<"Travel\n";}

voidMove() //覆蓋

{cout<<"BusLorry\n";}

};voidmain()

{BusLorryb;

b.Take();

b.Travel();

b.Move();

}

在本例中,基類(lèi)的Take函數(shù)不是虛函數(shù),通過(guò)虛擬繼承后只有一個(gè)副本。而虛函數(shù)Move在Bus和Lorry中都有一個(gè)定義體,BusLorry將繼承哪一個(gè)不能確定,只有對(duì)它重定義,以求覆蓋。7.6.5引入虛擬繼承的目的

面向?qū)ο蟪绦蛟O(shè)計(jì)的兩個(gè)原則是抽象和分類(lèi)。抽象的結(jié)果就是歸類(lèi),把一些共性歸入一個(gè)類(lèi)中,通過(guò)繼承實(shí)現(xiàn)類(lèi)庫(kù)重用。分類(lèi)的目的是使類(lèi)的層次分明,結(jié)構(gòu)合理。

有了虛擬繼承,就可避免在繼承關(guān)系中存在成員的重復(fù),并消除模糊性,使我們可以將更多的共性歸到基類(lèi)中,而在它的派生類(lèi)中又不會(huì)出現(xiàn)冗余。

有了虛擬繼承,類(lèi)庫(kù)就可得到更廣泛的重用,使我們把精力主要放在抽象和分類(lèi)上,這樣更有利于面向?qū)ο髴?yīng)用程序的開(kāi)發(fā)。7.7.1構(gòu)造方法

在繼承和組合關(guān)系中,派生類(lèi)的構(gòu)造函數(shù)必須激活所有基類(lèi)的構(gòu)造函數(shù),并把相應(yīng)的參數(shù)傳遞給它們。不提供參數(shù)或不給出基類(lèi)構(gòu)造函數(shù)就激活基類(lèi)的默認(rèn)構(gòu)造函數(shù),若此時(shí)基類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù),就會(huì)出現(xiàn)錯(cuò)誤。7.7對(duì)?象?的?構(gòu)?造7.7.2構(gòu)造順序

1.構(gòu)造虛基類(lèi)

先構(gòu)造并調(diào)用虛基類(lèi)的構(gòu)造函數(shù)。任何虛基類(lèi)的構(gòu)造函數(shù)是按照它們?cè)谂缮?lèi)表中出現(xiàn)的順序構(gòu)造的,而不是它們?cè)诔蓡T初始化表中的順序。

2.構(gòu)造非虛基類(lèi)

構(gòu)造并調(diào)用非虛基類(lèi)的構(gòu)造函數(shù)。任何非虛基類(lèi)的構(gòu)造函數(shù)是按照它們?cè)谂缮?lèi)表中出現(xiàn)的順序構(gòu)造的,而不是它們?cè)诔蓡T初始化表中的順序。

3.構(gòu)造組合類(lèi)

構(gòu)造并調(diào)用成員對(duì)象,即組合類(lèi)的構(gòu)造函數(shù)。任何成員對(duì)象的構(gòu)造函數(shù)是按照它們?cè)陬?lèi)中被聲明的順序構(gòu)造的,而不是它們?cè)诔蓡T初始化表中的順序。

4.構(gòu)造自己

構(gòu)造并調(diào)用類(lèi)自己的構(gòu)造函數(shù)。

【例7-12】演示并說(shuō)明構(gòu)造順序。#include<iostream>

usingnamespacestd;

classOBJ1

{public:

OBJ1(){cout<<"OBJ1\n";}

};

classOBJ2

{public:

OBJ2(){cout<<"OBJ2\n";}

};classBase1

{public:

Base1(){cout<<"Base1\n";}

};

classBase2

{public:

Base2(){cout<<"Base2\n";}

};

classBase3

{public:

Base3(){cout<<"Base3\n";}

};classBase4

{public:

Base4(){cout<<"Base4\n";}

};

classDerived:publicBase1,virtualpublicBase2,publicBase3,

virtualpublicBase4 //派生類(lèi)表

{public:

Derived()

:Base4(),Base3(),Base2(),Base1(),obj2(),obj1()

{cout<<"Derivedok.\n";}

protected:

OBJ1obj1;OBJ2obj2;//組合類(lèi)以聲明的次序決定構(gòu)造次序

};

voidmain()

{Derivedaa;cout<<“Thisisok.\n”;}

運(yùn)行結(jié)果:

Base2

Base4

Base1

Base3

OBJ1 //因?yàn)閛bj1在obj2前

OBJ2

Derivedok.

Thisisok.●?編譯時(shí)就能確定調(diào)用哪個(gè)函數(shù),稱為靜態(tài)聯(lián)編;運(yùn)行時(shí)

溫馨提示

  • 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)論