chapter4Word下载.docx
《chapter4Word下载.docx》由会员分享,可在线阅读,更多相关《chapter4Word下载.docx(94页珍藏版)》请在冰豆网上搜索。
可以将前一个类classstring定义为基类(baseclass):
然后另外定义一个类string_derived,称为派生类(derivedclass),如下:
classstring_derived:
string
{intcursor;
这个派生出的新类具有以下特点(主要是前两点):
1.派生类是基类定义的延续---在派生类中可隐含地具有基类的任何成员。
2.派生类是基类定义的扩充---在派生类中可重新定义新的成员(在第五章中还将看到,可在派生类中改变或扩充基类成员函数的功能)。
3.派生类可以是基类的组合---派生类可由多个基类派生而来,此时派生类是所有基类的属性和行为的组合。
这称为“可重用(可复用reusable)的软件构件”。
其中基类称baseclass,派生类称derivedclass,父类称ancesterclass,子类称subclass。
继承:
从一个或多个先前定义过的类(称为直接基类)中接受全部或一部分数据或函数(行为、操作),并且重新定义原有成员并补充定义新的成员,因而形成一个新的低层的类(称为直接派生类)。
而该派生类又可用作更低层派生类的直接基类。
这样就建立了类的层次(hierarchyofclasses)
可用有向无环图(DirectedAcyclicGraph,DAG)来表示继承关系。
DAG
string
private
string_derived
绝大部分书中的有向无环图(DirectedAcyclicGraph,DAG)的箭头向上,但只有J.Grabe:
UpandRunningwithC++书中箭头向下(而按照数据结构中”图”的定义箭头应向下)。
本课中不用箭头而用连线。
以后还将看到其他箭头用于表示参数传递关系和构造函数调用顺序。
4.1.2对象的内存存储内容(存储形式)
在建立对象之前,当用户程序被编译时,编译系统即为该程序分配内存数据空间(用于存储该类的静态数据)以及内存代码空间(用于存储成员函数)。
如第二章中所述,一个对象被建立后,系统又为该对象分配内存栈区空间,用于存储每个对象的非静态数据。
因此,用于存储对象数据成员的内存空间分为两部分:
即内存栈区空间以及内存数据空间。
当建立派生类的对象后,派生类对象的内存栈区空间中既包括基类部分的也包括派生类部分的所有非静态数据成员。
这些数据成员中不管是否允许访问(详见本章§
4.2),都一律存放在派生类对象的内存栈区空间中。
内存栈区空间内的非静态数据成员的排列顺序是基类部分在前,派生类部分在后。
如果有虚函数,则内存栈区空间内还包括虚指针VPTR,而该虚指针则指向内存数据空间内的虚函数地址表vtable。
(详见第五章)
应注意:
非静态数据存放在内存栈区空间内、用new动态分配的数据存放在内存堆区空间内,以及静态数据和全局变量存放在内存数据空间内,这三类数据决不存放在同一个内存空间内。
[例1]读取各对象(包括各派生类的对象)的长度及其栈区存储内容。
//obj_cont_3.cpp
//Toshowthesizesoftheclassesandthestoredcontents
//oftheobjectofthederivedclass
#include<
iostream.h>
classbase{
intx1;
base(inta){x1=a;
}
intread(){returnx1;
//Thisistoshowthatitdoesnotoccupyanyobjectspace
classderive:
base{
intx2;
derive(inta,intb):
base(a)
{x2=b;
classgrand:
derive{
intx3;
grand(inta,intb,intc):
derive(a,b)
{x3=c;
voidmain()
{
cout<
<
"
sizeofclassbaseis"
sizeof(base)<
endl;
sizeofclassderiveis"
sizeof(derive)<
sizeofclassgrandis"
sizeof(grand)<
basep(7);
deriveder(5,10);
grandgr(3,6,9),*ptr_gr;
ptr_gr=&
gr;
int*ptr=(int*)ptr_gr;
firstpartofgris"
*ptr++<
endl;
secondpartofgris"
thirdpartofgris"
*ptr<
}
/*Results:
sizeofclassbaseis4
sizeofclassderiveis8
sizeofclassgrandis12
firstpartofgris3
secondpartofgris6
thirdpartofgris9*/
以上程序中,对象的栈区存储内容只是各对象的非静态数据。
程序还显示出初始化列表的另一个用途:
从派生类构造函数向基类构造函数传递参数(此点下节将详述)。
base
derive
grand
各对象的栈区存储内容
对象p对象der对象gr
gr.base:
:
x1=3
p.x1=7
der.base:
x1=5
gr.derive:
x2=6
der.x2=10
gr.x3=9
4.1.3继承中的初始化和支配规则
在以上例子obj_cont_3.cpp中,派生类对象通过初始化列表对其基类部分实现初始化。
再看一下该程序的片断内容:
……
{x2=b;
{x3=c;
其中classgrand的构造函数的初始化列表derive(a,b)用于从classgrand向classderive传递参数a和b,以便将classderive对象初始化。
而classderive的构造函数的初始化列表base(a)用于从classderive向classbase传递参数a,以便将classbase对象初始化。
这是初始化列表的重要用途。
对初始化列表的要求
派生类构造函数的初始化列表及其直接基类的构造函数的参数表中的参数类型和数量必须完全相同。
在进一步了解初始化列表之前,先看一下支配规则。
支配规则
(派生类成员支配基类同名成员)
1.其它函数访问对象成员时,先访问本派生类对象中同名成员。
2.如果派生类对象中没有该同名成员,则进而访问该对象的直接基类部分的同名成员。
3.如果该对象的直接基类部分中仍然没有该同名成员,则不断上溯至该对象的间接基类部分中寻找,直至找到为止。
4.如仍找不到,则出现编译错误。
[例1]使用初始化列表(initializationtable)将基类部分的非静态数据初始化
//initab_2.cpp
classX
protected:
inti,j;
X(inti,intj){X:
i=i,X:
j=j;
voidprint(){cout<
"
i="
i<
j="
j<
classY:
publicX
intk;
Y(inti,intj):
X(i,j){k=i*j;
}//初始化列表
voidprint()
{cout<
k="
k<
classZ:
Y
Z(inti,intj):
Y(i,j){}//初始化列表
voidprint()
{Y:
print();
//因cout<
k;
不行,将会如下出错:
//error'
k'
:
cannotaccessprivatemember
//declaredinclass'
Y'
//所以只能用Y:
print()
//详见本章§
4.2“派生类成员函数和其它函数
//访问基类成员时的权限(访问控制表)”
}
Xobjx(5,10);
Yobjy(12,24);
Zobjz(25,50);
X对象:
;
objx.print();
Y对象:
objy.print();
Z对象:
objz.print();
i=5,j=10
i=12,j=24,k=288
i=25,j=50,k=1250*/
按照支配规则,以上每个类对象调用print()函数时,都只调用本类的函数。
请参照下图。
DAG
x(i,j)
public参
y(i,j)
数
传
z(i,j)
递
栈区存储内容
classx的对象objx
objx.i=5
objx.j=10
classy的对象objyclassz的对象objz
objz.x:
i=25
objy.x:
i=12
j=50
j=24
objz.y:
k=1250
objy.k=288
[例2]见以下本章§
4.4.1中[例3]
[例3]再看一下支配规则的例子:
//acc_mod_2_2.cpp
classA
{……
voidfa();
};
classB:
publicA
voidfb();
classC:
publicB
voidfc();
};
voidmain()
{Cobjc;
objc.fa();
........}
主函数想要调用fa(),先到classC中、再到classB中、最后在classA中找到。
在其它情况下,如还找不到,则程序出错。
4.1.4派生类对象为基类对象赋值
由以上§
4.1.2[例1]程序obj_cont_3.cpp中看出,由于派生类对象既包括自身部分的非静态数据成员,又包括基类部分的非静态数据成员,所以派生类对象的长度大于基类对象的长度。
它所包含的基类部分的非静态数据成员可用于对基类的另一个对象的非静态数据成员赋值。
见下例:
[例1]各级派生类对象der和gr为基类对象p赋值
//obj_assign_1.cpp
//objectsofderivedclassesofdifferentlevels
//areusedtoassignmembervaluetoobjectofbaseclass
voidshow(){cout<
x1<
publicbase{
publicderive{
//
(1)
//
(2)
grandgr(3,6,9);
//(3)
p.show();
//(4)
p=der;
//(5)派生类对象向基类对象赋值
//(6)
p=gr;
//(7)更下一级派生类对象向基类对象越级赋值
//(8)
//但反过来不行//der=p;
//error:
binary'
='
nooperatordefinedwhichtakesaright-handoperand
//oftype'
classbase'
(orthereisnoacceptableconversion)
7
5
3*/
以上程序中p=der;
和p=gr;
两条语句分别使用各派生类对象der和gr中的der.x1(=5)和gr.x1(=3)对基类数据成员x1赋值。
不同赋值结果分别为5和3。
各级派生类对象的内存栈区空间的存储内容(简称栈区存储内容)见下图。
主函数前三句运行后(§
4.1.2[例1]中已有)
对象p对象der对象gr
主函数第五句“p=der;
”运行后
对象p对象der
p.x1=5
主函数第七句“p=gr;
对象p对象gr
p.x1=3
§
4.4.2.1“调用顺序”中还有[例2]mul_inh_4.cpp供有兴趣者参照。
4.2派生类成员函数和其它函数访问基类成员时的权限(访问控制表)
4.2.1类成员的访问控制(accesscontrol)
(1)类成员的访问说明符(accessspecifier):
默认值为私有,可声明为公有或保护;
私有(private)成员的访问属性:
只能供本类的成员函数和友员函数访问,不准其它函数访问;
公有(public)成员的访问属性:
可供任何函数访问;
保护(protected)成员的访问属性:
除与私有成员相同外,还允许派生类成员函数访问,但不准其它函数访问。
[重要注解]此处其它函数系指主程序(主函数)、其他类的成员函数和普通函数等。
(2)结构(struct)成员的访问控制符:
默认值为公有,可声明为私有或保护;
(3)联合(union)成员的访问控制符:
只能为公有。
这也可用三句话表达:
1.其它函数只能访问对象中公有成员。
2.成员函数能够访问对象中所有成员。
3.派生类成员函数不能访问对象中基类私有成员。
例如:
[例1]基类的访问控制情况
//acc_mod_base.cpp
classB
inti;
//private
intj;
B(inta,intb,intc){i=a;
j=b;
k=c;
voidf(){cout<
'
'
};
Bobj(1,2,3);
//cout<
obj.i;
//notallowed
obj.j;
obj.k<
obj.f();
3
123*/
其中classB的成员f()能够访问所有成员i,j,k,但主程序只能访问classB对象中的公有成员k和f()。
可将此访问权限列于以下表内。
TABLE1(classB的访问控制表)
classname
public
protected
f(),k
j
i
上述访问控制表内包含符合访问权限的数据成员和成员函数,也即:
本类(直接基类)的成员函数有权访问访问控制表内的所有成员。
派生类的成员函数只能访问具有public和protected属性的成员。
而其他函数只能访问具有public属性的成员。
与访问属性相同,派生(继承)方式也共有三种:
(1)公有(public)继承方式
(2)私有(private)继承方式
(3)保护(protected)继承方式
三种访问属性和三种派生(继承)方式形成了访问权限的九种不同组合,也就是说,成员的访问属性随着继承方式的不同而不同。
下面详细分析。
4.2.2派生类对基类成员的继承
已经知道,一个类中的成员函数能够访问同一类中的所有成员,无论是公有的或私有的都可以访问。
试问:
该基类的派生类中的成员函数也能够访问该基类中的所有成员吗?
答案不是一句话能包括的,因为这还决定于继承方式。
4.2.2.1公有继承方式
既然希望派生类中的成员函数能够访问尽量多的基类成员,当然希望应用公有继承方式。
如下例:
//pub_inh_1.cpp
//Toseeiffunctionofpublicly-derivedclasscanaccessbasemembers
{intib;
intjb;
intkb;
B(inti=1,intj=2,intk=3){ib=i;
jb=j;
kb=k;
classD:
publicB//公有继承
voidfun_d()
fun_d()inclassDreadsjb="
jb<
kb="
kb<
//cout<
ib;
//error:
ib'
cannotaccessprivatememberdeclaredinclass'
B'
//只能访问基类中保护和公有成员
classG:
publicD//公有继承的更下一层派生类
voidfun_g()
fun_g()inclassGreadsjb="
}
Dobjd;
objd.fun_d();
main()readsobjd.kb="
objd.kb<