版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 遼寧石化職業(yè)技術(shù)學(xué)院《審計(jì)流程實(shí)驗(yàn)》2023-2024學(xué)年第一學(xué)期期末試卷
- 昆明幼兒師范高等專(zhuān)科學(xué)?!渡鐣?huì)科學(xué)名著》2023-2024學(xué)年第一學(xué)期期末試卷
- 江西傳媒職業(yè)學(xué)院《機(jī)械制造技術(shù)基礎(chǔ)實(shí)驗(yàn)》2023-2024學(xué)年第一學(xué)期期末試卷
- 吉林師范大學(xué)博達(dá)學(xué)院《課外讀寫(xiě)實(shí)踐》2023-2024學(xué)年第一學(xué)期期末試卷
- 湖南商務(wù)職業(yè)技術(shù)學(xué)院《電子線路CAD設(shè)計(jì)》2023-2024學(xué)年第一學(xué)期期末試卷
- 湖南財(cái)政經(jīng)濟(jì)學(xué)院《中國(guó)民族民間舞(一)》2023-2024學(xué)年第一學(xué)期期末試卷
- 黑龍江三江美術(shù)職業(yè)學(xué)院《中文工具書(shū)》2023-2024學(xué)年第一學(xué)期期末試卷
- 重慶工業(yè)職業(yè)技術(shù)學(xué)院《經(jīng)濟(jì)地理學(xué)》2023-2024學(xué)年第一學(xué)期期末試卷
- 浙江科技學(xué)院《材料綜合實(shí)驗(yàn)》2023-2024學(xué)年第一學(xué)期期末試卷
- 年產(chǎn)2萬(wàn)噸鹽酸二甲雙胍原料藥項(xiàng)目可行性研究報(bào)告模板-立項(xiàng)備案
- 2023年全國(guó)統(tǒng)一高考數(shù)學(xué)甲卷【文科+理科】試題及答案解析
- 社區(qū)團(tuán)支部工作計(jì)劃
- 廢品處置招標(biāo)書(shū)
- GA/T 1280-2024銀行自助設(shè)備安全性規(guī)范
- 數(shù)據(jù)標(biāo)注基地項(xiàng)目實(shí)施方案
- 靜脈治療專(zhuān)科護(hù)士競(jìng)聘
- 2024年第一季度醫(yī)療安全(不良)事件分析報(bào)告
- 中醫(yī)課件英語(yǔ)教學(xué)課件
- 《哪吒鬧海》電影賞析
- 2024年初一英語(yǔ)閱讀理解專(zhuān)項(xiàng)練習(xí)及答案
- 《建筑工程設(shè)計(jì)文件編制深度規(guī)定》(2022年版)
評(píng)論
0/150
提交評(píng)論