C++程序设计第10章 继承.docx

上传人:b****2 文档编号:2570621 上传时间:2023-05-04 格式:DOCX 页数:38 大小:155.28KB
下载 相关 举报
C++程序设计第10章 继承.docx_第1页
第1页 / 共38页
C++程序设计第10章 继承.docx_第2页
第2页 / 共38页
C++程序设计第10章 继承.docx_第3页
第3页 / 共38页
C++程序设计第10章 继承.docx_第4页
第4页 / 共38页
C++程序设计第10章 继承.docx_第5页
第5页 / 共38页
C++程序设计第10章 继承.docx_第6页
第6页 / 共38页
C++程序设计第10章 继承.docx_第7页
第7页 / 共38页
C++程序设计第10章 继承.docx_第8页
第8页 / 共38页
C++程序设计第10章 继承.docx_第9页
第9页 / 共38页
C++程序设计第10章 继承.docx_第10页
第10页 / 共38页
C++程序设计第10章 继承.docx_第11页
第11页 / 共38页
C++程序设计第10章 继承.docx_第12页
第12页 / 共38页
C++程序设计第10章 继承.docx_第13页
第13页 / 共38页
C++程序设计第10章 继承.docx_第14页
第14页 / 共38页
C++程序设计第10章 继承.docx_第15页
第15页 / 共38页
C++程序设计第10章 继承.docx_第16页
第16页 / 共38页
C++程序设计第10章 继承.docx_第17页
第17页 / 共38页
C++程序设计第10章 继承.docx_第18页
第18页 / 共38页
C++程序设计第10章 继承.docx_第19页
第19页 / 共38页
C++程序设计第10章 继承.docx_第20页
第20页 / 共38页
亲,该文档总共38页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

C++程序设计第10章 继承.docx

《C++程序设计第10章 继承.docx》由会员分享,可在线阅读,更多相关《C++程序设计第10章 继承.docx(38页珍藏版)》请在冰点文库上搜索。

C++程序设计第10章 继承.docx

C++程序设计第10章继承

第10章继承(Inheritances)

继承是C++语言的一种重要机制,该机制使类与类之间可以建立一种上下级关系,可以自动地提供来自另一个类的操作和数据结构来创建新类,程序员只需在新类中定义已有类中没有的成分来建立新类。

继承是面向对象程序设计的重要基础。

10.1继承结构(InheritantStructures)

1类层次结构(ClassHierarchies)

图10_01展示了交通工具的类层次,最顶部的交通工具类称为基类。

这个基类有飞机类,汽车类和火车类,交通工具类就是它们的父类。

还可以从交通工具类派生(derived)出其他类,例如,轮船类等。

每个类都只以交通工具类作为其父类。

汽车类相对于交通工具类来说是子类,所以,在本图的类系中,也可以称汽车子类。

汽车子类还有三个子类,小汽车类,大卡车类,大客车类,每个子类都以汽车类作为父类,此时的交通工具类则是它们的祖先类,简称祖类。

另外,小汽车类派生了轿车类,工具车类和小面包车类。

图中展示了小型的5个层次的类系,从上到下它们是派生的关系,从下到上是继承的关系。

每个类都有且只有一个父类,除了最顶层的交通工具类,其他所有的类都可以是子类,当一个类派生出其他类的时候,自己便是父类了。

继承关系使得我们得以用一种简单的方式来描述事物。

例如,描述什么叫鸭子,可以回答说,它是一种嘎嘎叫的鸟。

鸭子是一种鸟,所以鸭子是鸟类的派生,但鸭子又具有嘎嘎叫的特征,嘎嘎叫是区别于其他同类鸟的属性。

由于鸟类通常的特征大家都清楚,例如,有翅膀,有两只脚等,所以用鸟类来描述鸭子,只要举出鸭子自己所具有的特点就行了。

继承使我们描述事物的能力大大增强和直截了当。

描述事物,一般是从属性和操作上来描述的,例如,日期具有年、月、日的属性,显示日期,闰年判断都可以认为是日期所具有的操作。

把这种继承关系引入程序设计,目的是希望过去描述(定义)的类能够作为基类,来进一步详细地描述派生的事物。

这样一来,派生事物的描述基于基类,就可以简单化了。

对于一个问题的解决,事先有一些基类,即一些资源,这些基类,已经具有一定的数据描述和较强的数据操作能力了,籍此,通过简单描述事物的属性和操作,就可以继承基类的强大功能,为我所用,方便程序设计了。

继承就是让子类继承父类的属性和操作,子类可以声明新的属性和操作,还可以剔除那些不适合其用途的父类操作。

在新的应用中,父类的代码已经存在,无须修改父类。

所要做的是派生子类,在子类中增加和修改。

所以,继承可以让你重用父类的代码,专注于为子类编写新代码。

在没有采用继承技术之前,程序重用的是过程代码,或者说操作,例如C语言所附的库函数便是一种编程资源,提供给我们一定程度的过程代码重用。

现实世界是分类分层的客观实在,物质有无机与有机之分,有机体有生命体与非生命体之分,生命体有动物,植物,微生物之分,动物有高等与低等,人是高等动物,人有各个种族,……

继承也是我们理解事物,解决问题的方法,继承帮助我们描述事物的层次关系,帮助我们如何有效地精确地描述事物,帮助我们理解事物直至本质。

一旦看清了事物所处的层次结构位置,也就可以找到相关的解决办法。

继承可以使已经存在的类不需修改地适应新应用,继承是比过程重用规模更广的重用,是已经定义的良好的类的重用。

2对象本体(ObjectReality)

如果类BaseClass是基类:

classBaseClass{

inta,b;

//otherprivatemembers

public:

//publicmembers

};

则其对象本体含有两个整型空间。

派生类继承的方式是在类定义的class类名的后面加上:

public再加上基类名。

如果B继承了BaseClass类,则:

classB:

publicBaseClass{

intc;

//otherprivatemembers

public:

//publicmembers

};

派生类对象本体包括两个部分,一个为基类部分,即含两个整型空间,另一个为派生类部分,含一个整型空间。

如图10_02所示。

这是一个示意图,表示派生类与基类不可分割的关系,派生类总是依附于基类,派生类对象中总是含有基类对象,即含有基类的数据成员。

或者说,基类对象是派生类对象的组成部分。

至于具体的实现中,空间的安排,并不一定基类排在前,派生类排在后。

显然,派生类对象通常比基类对象大,它保存了更多的数据,提供了更多的操作。

基类也称超类,派生类也称子类,读者必须接受超类的数据反而比子类的数据少这个事实。

10.2成员函数(MemberFunction)

1继承父成员(InheritFather’sMember)

在类中,还有一种保护(protected)型的访问符,保护成员与私有成员一样,不能被使用类的程序员进行公共访问,可以被类内部的成员函数访问,除此之外,如果使用类者是派生类成员,则可以被访问,这是私有成员所不具有的能力。

也就是说,只要将类成员声明成保护成员,则其派生类在继承之后,就可以坐享其父类的公有和保护操作了。

例如,有一个学生类Student,现在要增加研究生类,研究生类除了自己所特有的性质外,具有学生类的所有性质,所以我们用继承的方法来重用学生类:

1.//=====================================

2.//f1001.cpp

3.//inheritance

4.//=====================================

5.#include

6.usingnamespacestd;

7.//-------------------------------------

8.classAdvisor{//导师

9.intnoOfMeeting;

10.};//-----------------------------------

11.classStudent{

12.stringname;

13.intsemesterHours;

14.doubleavge;

15.public:

16.Student(stringpName="noName"):

name(pName),average(0),semesterHours(0){}

17.voidaddCourse(inthours,doublegrade){

18.doubletotalGrade=(semesterHours*average+grade);//总分

19.semesterHours+=hours;//总修学时

20.average=semesterHours?

totalGrade/semesterHours:

0;//平均分

21.}

22.voiddisplay(){

23.cout<<"name=\""<

24.<<",average="<

25.}

26.intgetHours(){returnsemesterHours;}

27.doublegetAverage(){returnaverage;}

28.};//-----------------------------------

29.classGraduateStudent:

publicStudent{

30.Advisoradvisor;

31.intqualifierGrade;

32.public:

33.getQualifier(){returnqualifierGrade;}

34.};//-----------------------------------

35.intmain(){

36.Studentds("Loleeundergrade");

37.GraduateStudentgs;

38.ds.addCourse(3,2.5);

39.ds.display();

40.gs.addCourse(3,3.0);

41.gs.display();

42.}//====================================

ds是Student类对象,gs是GraduateStudent类对象。

作为Student的子类,对象gs可以做ds能做的任何事情,它有name,semesterHours,average数据成员,以及addCourse()成员函数,此外它还比ds多一点东西,即它有导师Advisor和资格考试分qualifierGrade。

如果GraduateStudent类不继承Student类,则gs对象中就没有ds成分,即学时和平均分数据成员都没有,也不能以gs的名义进行addCourse操作。

若要使用addCourse操作,则必须以Student对象的名义。

可见,没有继承的研究生类对象必须将Student类拿来,对里面的关键代码进行复制,重新编写和组织类,这种借鉴作用,比完全拿来重用,要落后很多。

何况玩真格儿的规模化程序设计还没有开始,彼此之间相差的工作量真不敢说。

现在,gs对象也是一个学生,所以对Student中的addCourse()成员函数的调用,等于是在调用自己的成员函数。

正是由于gs是一个学生,所以,将gs赋值给Student对象也是合情合理的。

因为gs也能做Student对象所能做的任何事。

即:

GraduateStudentgs;

Students(gs);//ok

Student&t=gs;//ok

Student*t=&gs;//ok

事实上,gs的对象实体中包含有Student对象实体。

将gs赋值给Student对象s,就是将gs中的Student对象实体部分,拷贝给s。

将gs初始化Student对象的引用t,就是将t作为gs中的Student对象实体的别名。

因此,以基类对象作形参,以派生类对象作为实参的函数调用,也是合理的设计。

例如,将研究生对象传递给学生类对象的引用:

voidfn(Student&s){

//任何s想要做的事

}

voidgn(){

GraduateStudentgs;

fn(gs);

}

2类内访问控制(AccessControlinClass)

继承可以公共继承,也可以保护继承和私有继承。

多数情况是公有继承,就像前面所看到的。

也就是说,在class类名后面加上public关键字再加基类名称。

对于保护继承和私有继承,见CH15.6。

公共继承,反映了派生类对基类所有成员原封不动的访问控制权限的继承。

公有和保护操作的继承。

和派生类之间的

继承了基类,并不是说派生类就能访问基类的私有成员了。

如果是那样的话,那些使用基类的人,靠派生,就能使用基类的私有成员了:

classBase{

inta;

public:

voidprint(){cout<

//publicmembers

};

classDerived:

publicBase{

intb;

public:

voiddisplay(){cout<

//otherpublicmembers

};

voidfn(){

Derivedd;

d.print();//ok

d.display();//能访问Base类的私有成员吗?

cout<

}

这样一来,本来任何由基类引起的错误,就并不一定是由基类自己引起的了,有可能是其后裔捣鬼,程序调试因而变得复杂起来了。

而且类变得一点隐私都没有了,使用类者想用类的私有数据,只要轻言继承,就能访问基类的一切。

类机制一方面通过访问控制提供屏蔽类,分离类的实现的能力,使得编程职责分离,另一方面却又允许继承的子女毫无遮挡地访问其私有成员,等于让人人都可以越过访问控制而去访问私有成员,这是类机制肯定不能允许的。

一个类,将外界能够访问的操作都公有化了,所有不能被外界访问的成员都私有化,这样一来,在后继的类中,就没有对基类任何可以悄悄改进的余地了。

所以,继承也需要这样的成员,它们对外界是私有的,对派生的子女是允许访问的。

这种设计需求就是访问控制符protected。

classBase{

inta;

voidf(){cout<

public:

intb;

voidg(){cout<

protected:

intc;

voidk(){cout<

};//-----------------------------------

classDerived:

publicBase{

public:

voiddf(){

cout<

cout<

cout<

f();//error

g();//ok

k();//ok

}

};//-----------------------------------

voidfunc(){

Baseb;

cout<

cout<

cout<

b.f();//error

b.g();//error

b.k();//ok

}//------------------------------------

一方面,基类提供公有操作界面,public成员对一切使用者公开;另一方面,基类还有自己的隐私,private成员只对自己的成员开放,但即使是自己的后裔也保密;(父母亲过去的恋爱史对自己的子女或许还羞于启口,成为隐私吧)还有一方面,基类也提供只向自己的后裔开放的成员,便于继承者基于此而改进。

因此保护(protected)成员通常是基类为后裔所做的专业或精巧的公有界面,后裔可以访问保护成员函数和保护数据,最终向使用者提供公有界面。

保护成员函数一般不是原始的基本的操作,否则作为私有成员通过公有成员去访问就得了。

或许这些操作很高效很优秀,其后裔可以考虑将其公有化,但目前该界面还有可能会改变。

种种原因,使得这些成员既不能私有,也不能公有。

基类为了长远考虑,可以留下保护成员,但派生类简单而清晰的设计应该是不使用任何保护成员,即只使用公有成员。

10.3派生类的构造(ConstructingDerivedClasses)

1默认构造(DefaultConstruction)

派生类也是类,如果没有定义构造函数,则根据类机制,将会执行默认构造函数。

派生类的默认构造函数会首先调用父类的无参构造函数,如果父类定义了构造函数(因此没有默认构造函数),又没有定义无参构造函数,则会导致编译发怒。

如果父类还有父类,则父类会先调用父类的父类的无参构造函数,依此类推。

在程序f1001.cpp中,研究生类只有默认构造函数,默认构造函数首先要调用Student无参构造函数来创建Student对象。

因此调用了Student默认参数的构造函数(无参形式)。

所以输出的结果中名字为“noName”。

在构造一个子类时,完成其基类部分的构造由基类的构造函数去做,将基类对象看作是完全独立于派生类的对象,这样做的好处是,一旦基类的实现有错误,只要不涉及界面,那么,基类实现中的修改不会影响派生类的操作。

类与类之间,你做你的,我做我的,职责分明,即使父子继承关系的类之间也不例外。

2自定义构造(SelfDefinedConstruction)

可以在派生类的构造函数中规定调用基类构造函数的形式,并不是非得要调用无参构造函数的。

在下列程序中,GraduateStudent类定义了一个构造函数,它调用了基类的构造函数,因而研究生就变得有名有姓了:

1.//=====================================

2.//f1002.cpp

3.//constructingderivedclass

4.//=====================================

5.#include

6.usingnamespacestd;

7.//-------------------------------------

8.classAdvisor{

9.intnoOfMeeting;

10.public:

11.Advisor(){cout<<”Adviosr\n”;}

12.Advisor(){cout<<”copyAdvisor\n”;}

13.~Advisor(){cout<<”~Advisor\n”;}

14.};//-----------------------------------

15.classStudent{

16.stringname;

17.intsemesterHours;

18.doubleaverage;

19.public:

20.Student(stringpName="noName"):

name(pName),average(0),semesterHours(0){}

21.voidaddCourse(inthours,doublegrade){

22.doubletotalGrade=(semesterHours*average+grade);//总分

23.semesterHours+=hours;//总修学时

24.average=semesterHour?

totalGrade/semesterHours:

0;//平均分

25.}

26.voiddisplay(){

27.cout<<"name=\""<

28.<<",average="<

29.}

30.intgetHours(){returnsemesterHours;}

31.doublegetAverage(){returnaverage;}

32.~Student(){cout<<”~Student\n”;}

33.};//-----------------------------------

34.classGraduateStudent:

publicStudent{

35.Advisoradvisor;

36.intqualifierGrade;

37.public:

38.GraduateStudent(conststring&pN,Advisor&adv)

39.:

Student(pN),advisor(adv),qualifierGrade(0){}

40.voiddisplay(){

41.Student:

:

display();

42.cout<<"GraduateStudent\n";

43.}

44.getQualifier(){returnqualifierGrade;}

45.};//-----------------------------------

46.voidfn(Advisor&advisor){

47.GraduateStudentgs("YenKayDoodle",advisor);

48.gs.display();

49.}//------------------------------------

50.intmain(){

51.Advisorda;

52.fn(da);

53.}//====================================

调用基类的构造函数的形式与对象成员初始化形式相似,都是放在构造函数的初始化列表中。

在第43行有一条调用派生类成员函数display的语句,该语句先调用基类的display()语句,然后再执行自己特有的操作,输出研究生字样。

调用基类display的第36行语句中为了要区分基类与派生类的display的不同,在display前要加类名Student。

如果不定义派生类的display,gs对象也可以通过点操作符调用基类的display操作,这时候,编译就认定是基类的display操作了,因为派生类没有display成员。

在派生类中定义一个相同名字的操作,目的是表明与基类操作的相似性,并在此基础上,加上自己特有的操作。

另一个原因是便于进一步派生,类层次分明。

3拷贝构造与复制(CopyConstruction&Assignment)

拷贝构造的方式与构造函数的方式相似,也就是说,基类若没有自定义拷贝构造函数,则派生类的拷贝构造函数将调用基类的默认拷贝构造函数,否则,调用基类的自定义拷贝构造函数。

派生类若没有自定义拷贝构造函数,则拷贝构造时调用默认拷贝构造函数。

这对于对象本体与对象实体不一致的情况来说,需要在派生类中自定义拷贝构造函数。

拷贝构造时面临的问题是派生类对象拷贝给基类对象的转换问题。

这时,将按照参数传递规则办事。

例如:

classStudent{

Student&operator=(constStudent&);

Student(constStudent&s);

};//-------------------------------------------

voidfn(constGraduateStudent&gs){

Studentss=gs;

ss=gs;

}//--------------------------------------------

根据参数传递规则,gs将匹配Student(constStudent&s)的拷贝构造函数中的形参s,即:

constStudent&s=gs;

因此s将指向gs对象中的基类部分。

完成拷贝构造时,即将gs中的基类对象拷贝复制给ss。

同理,在赋值时,有相似过程。

4构造顺序(ConstructionOrder)

派生类构造函数被调用时,在还没有执行构造函数体之前,立刻调用基类的构造函数。

如果基类构造调用在初始化列表中有的,就按初始化列表的调用形式做,否则,就调用相应的基类无参构造函数。

同理,如果基类上面还有基类,则也会优先调用上面的基类构造函数的。

做完了基类的构造函数,接下来就要给自身的对象本体分配空间了,然后,就调用对象中的各个对象成员的构造函数。

一边调用一边分配空间。

如果有多个对象,其调用的顺序按类中对象定义的顺序排定。

任何构造函数的调用,总是先分配好对象本体的空间,给出该空间的this指针,然后,笃笃定定地执行构造函数的体。

因此,一个含有基类对象的对象,含有对

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2