C++程序设计第10章 继承文档格式.docx
- 文档编号:4770256
- 上传时间:2023-05-04
- 格式:DOCX
- 页数:38
- 大小:155.28KB
C++程序设计第10章 继承文档格式.docx
《C++程序设计第10章 继承文档格式.docx》由会员分享,可在线阅读,更多相关《C++程序设计第10章 继承文档格式.docx(38页珍藏版)》请在冰点文库上搜索。
2对象本体(ObjectReality)
如果类BaseClass是基类:
classBaseClass{
inta,b;
//otherprivatemembers
public:
//publicmembers
};
则其对象本体含有两个整型空间。
派生类继承的方式是在类定义的class类名的后面加上:
public再加上基类名。
如果B继承了BaseClass类,则:
classB:
publicBaseClass{
intc;
//publicmembers
派生类对象本体包括两个部分,一个为基类部分,即含两个整型空间,另一个为派生类部分,含一个整型空间。
如图10_02所示。
这是一个示意图,表示派生类与基类不可分割的关系,派生类总是依附于基类,派生类对象中总是含有基类对象,即含有基类的数据成员。
或者说,基类对象是派生类对象的组成部分。
至于具体的实现中,空间的安排,并不一定基类排在前,派生类排在后。
显然,派生类对象通常比基类对象大,它保存了更多的数据,提供了更多的操作。
基类也称超类,派生类也称子类,读者必须接受超类的数据反而比子类的数据少这个事实。
10.2成员函数(MemberFunction)
1继承父成员(InheritFather’sMember)
在类中,还有一种保护(protected)型的访问符,保护成员与私有成员一样,不能被使用类的程序员进行公共访问,可以被类内部的成员函数访问,除此之外,如果使用类者是派生类成员,则可以被访问,这是私有成员所不具有的能力。
也就是说,只要将类成员声明成保护成员,则其派生类在继承之后,就可以坐享其父类的公有和保护操作了。
例如,有一个学生类Student,现在要增加研究生类,研究生类除了自己所特有的性质外,具有学生类的所有性质,所以我们用继承的方法来重用学生类:
1.//=====================================
2.//f1001.cpp
3.//inheritance
4.//=====================================
5.#include<
iostream>
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=\"
name<
\"
hours="
semesterHours
24.<
average="
average<
\n"
;
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;
Student*t=&
gs;
事实上,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;
voidprint(){cout<
a;
classDerived:
publicBase{
intb;
voiddisplay(){cout<
}//过分
//otherpublicmembers
voidfn(){
Derivedd;
d.print();
d.display();
//能访问Base类的私有成员吗?
cout<
d.a;
//更过分
这样一来,本来任何由基类引起的错误,就并不一定是由基类自己引起的了,有可能是其后裔捣鬼,程序调试因而变得复杂起来了。
而且类变得一点隐私都没有了,使用类者想用类的私有数据,只要轻言继承,就能访问基类的一切。
类机制一方面通过访问控制提供屏蔽类,分离类的实现的能力,使得编程职责分离,另一方面却又允许继承的子女毫无遮挡地访问其私有成员,等于让人人都可以越过访问控制而去访问私有成员,这是类机制肯定不能允许的。
一个类,将外界能够访问的操作都公有化了,所有不能被外界访问的成员都私有化,这样一来,在后继的类中,就没有对基类任何可以悄悄改进的余地了。
所以,继承也需要这样的成员,它们对外界是私有的,对派生的子女是允许访问的。
这种设计需求就是访问控制符protected。
voidf(){cout<
voidg(){cout<
protected:
voidk(){cout<
voiddf(){
//error
b;
c;
f();
g();
k();
voidfunc(){
Baseb;
b.a;
b.b;
b.c;
b.f();
b.g();
b.k();
}//------------------------------------
一方面,基类提供公有操作界面,public成员对一切使用者公开;
另一方面,基类还有自己的隐私,private成员只对自己的成员开放,但即使是自己的后裔也保密;
(父母亲过去的恋爱史对自己的子女或许还羞于启口,成为隐私吧)还有一方面,基类也提供只向自己的后裔开放的成员,便于继承者基于此而改进。
因此保护(protected)成员通常是基类为后裔所做的专业或精巧的公有界面,后裔可以访问保护成员函数和保护数据,最终向使用者提供公有界面。
保护成员函数一般不是原始的基本的操作,否则作为私有成员通过公有成员去访问就得了。
或许这些操作很高效很优秀,其后裔可以考虑将其公有化,但目前该界面还有可能会改变。
种种原因,使得这些成员既不能私有,也不能公有。
基类为了长远考虑,可以留下保护成员,但派生类简单而清晰的设计应该是不使用任何保护成员,即只使用公有成员。
10.3派生类的构造(ConstructingDerivedClasses)
1默认构造(DefaultConstruction)
派生类也是类,如果没有定义构造函数,则根据类机制,将会执行默认构造函数。
派生类的默认构造函数会首先调用父类的无参构造函数,如果父类定义了构造函数(因此没有默认构造函数),又没有定义无参构造函数,则会导致编译发怒。
如果父类还有父类,则父类会先调用父类的父类的无参构造函数,依此类推。
在程序f1001.cpp中,研究生类只有默认构造函数,默认构造函数首先要调用Student无参构造函数来创建Student对象。
因此调用了Student默认参数的构造函数(无参形式)。
所以输出的结果中名字为“noName”。
在构造一个子类时,完成其基类部分的构造由基类的构造函数去做,将基类对象看作是完全独立于派生类的对象,这样做的好处是,一旦基类的实现有错误,只要不涉及界面,那么,基类实现中的修改不会影响派生类的操作。
类与类之间,你做你的,我做我的,职责分明,即使父子继承关系的类之间也不例外。
2自定义构造(SelfDefinedConstruction)
可以在派生类的构造函数中规定调用基类构造函数的形式,并不是非得要调用无参构造函数的。
在下列程序中,GraduateStudent类定义了一个构造函数,它调用了基类的构造函数,因而研究生就变得有名有姓了:
2.//f1002.cpp
3.//constructingderivedclass
8.classAdvisor{
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="
21.voidaddCourse(inthours,doublegrade){
22.doubletotalGrade=(semesterHours*average+grade);
//总分
23.semesterHours+=hours;
24.average=semesterHour?
26.voiddisplay(){
27.cout<
28.<
29.}
30.intgetHours(){returnsemesterHours;
31.doublegetAverage(){returnaverage;
32.~Student(){cout<
”~Student\n”;
33.};
34.classGraduateStudent:
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指针,然后,笃笃定定地执行构造函数的体。
因此,一个含有基类对象的对象,含有对
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+程序设计第10章 继承 C+ 程序设计 10