chapter4.docx

上传人:b****6 文档编号:5011394 上传时间:2022-12-12 格式:DOCX 页数:94 大小:45.63KB
下载 相关 举报
chapter4.docx_第1页
第1页 / 共94页
chapter4.docx_第2页
第2页 / 共94页
chapter4.docx_第3页
第3页 / 共94页
chapter4.docx_第4页
第4页 / 共94页
chapter4.docx_第5页
第5页 / 共94页
点击查看更多>>
下载资源
资源描述

chapter4.docx

《chapter4.docx》由会员分享,可在线阅读,更多相关《chapter4.docx(94页珍藏版)》请在冰豆网上搜索。

chapter4.docx

chapter4

第四章继承性(inheritance)与类的派生(derivation)

4.1派生类(derivedclass)及其对象(object)

4.1.1定义

第三章中提到,在面向对象程序设计语言中,“类”是一组具有相同数据结构和相同操作(方法、函数)的集合,是一系列具有相同性质的对象的抽象内容,它描述的不是个别对象而是全体对象的共同特征。

“类”是具有相同共性的各事物的集合,是这些事物的统一抽象内容。

C++中,“类”被表达为一个具有特定功能的程序块,它提供代码共享(代码重用性),以便用户可以方便地建立所需要的任何数据类型。

但一个“类”(基类)无法包含这些事物的全部共性,而只包含主要共性。

为包含其它次要共性,可通过继承机制使用其它类(称为派生类)。

继承是C++面向对象程序设计的重要特性之一。

所谓继承,是建立一个新的类(即直接派生类),从一个或多个已经定义的类(称为直接基类)中继承一部分或全部函数和数据,同时还能重新定义或增加新的数据和函数。

继承机制在对象之间建立了派生关系,从而建立类的层次或等级(hierarchyofclasses)。

引入继承机制的目的是实现代码重用性(reusability):

一方面可以重用先前的代码,避免不必要的重复设计;另一方面,如果原代码不能完全满足要求,可以在绝不改变原有代码的情况下,补充新的代码,增加新的功能。

请见下图:

圆形

正方形

平行四边形

矩形

四边形

三角形

几何图形

几何图形

上图中,“几何图形”是基类,它包含了几何图形的一些基本共性。

它的派生类“三角形”、“四边形”和“圆形”各自包含了本图形的一些次要共性。

例如计算面积的表达式,这三个图形都不相同。

而“四边形”又可派生出“正方形”、“平行四边形”和“矩形”,从而使它们各自具有不同属性。

又例如,先有一个类为:

classstring

{intlength;

char*contents;

public:

string();

intget_length();

};

过些时候,需要增加功能,设计一个新类:

classedit_string

{intlength;

char*contents;

intcursor;

public:

edit_string();

intget_length();

intget_cursor_pos();

};

从中看出,这两个类的内容有不少重复。

后一个类应该可以继承前一个类的全部或一部分内容(在后一个类中用红色标出)。

可以将前一个类classstring定义为基类(baseclass):

然后另外定义一个类string_derived,称为派生类(derivedclass),如下:

classstring_derived:

string

{intcursor;

public:

edit_string();

intget_cursor_pos();

};

这个派生出的新类具有以下特点(主要是前两点):

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

classbase{

intx1;

public:

base(inta){x1=a;}

intread(){returnx1;}

//Thisistoshowthatitdoesnotoccupyanyobjectspace

};

classderive:

base{

intx2;

public:

derive(inta,intb):

base(a)

{x2=b;}

};

classgrand:

derive{

intx3;

public:

grand(inta,intb,intc):

derive(a,b)

{x3=c;}

};

voidmain()

{

cout<<"sizeofclassbaseis"<

cout<<"sizeofclassderiveis"<

cout<<"sizeofclassgrandis"<

basep(7);

deriveder(5,10);

grandgr(3,6,9),*ptr_gr;

ptr_gr=&gr;

int*ptr=(int*)ptr_gr;

cout<<"firstpartofgris"<<*ptr++<

cout<<"secondpartofgris"<<*ptr++<

cout<<"thirdpartofgris"<<*ptr<

}

/*Results:

sizeofclassbaseis4

sizeofclassderiveis8

sizeofclassgrandis12

firstpartofgris3

secondpartofgris6

thirdpartofgris9*/

以上程序中,对象的栈区存储内容只是各对象的非静态数据。

程序还显示出初始化列表的另一个用途:

从派生类构造函数向基类构造函数传递参数(此点下节将详述)。

DAG

base

 

private

derive

 

private

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中,派生类对象通过初始化列表对其基类部分实现初始化。

再看一下该程序的片断内容:

classbase{

intx1;

public:

base(inta){x1=a;}

……

};

classderive:

base{

intx2;

public:

derive(inta,intb):

base(a)

{x2=b;}

};

classgrand:

derive{

intx3;

public:

grand(inta,intb,intc):

derive(a,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

#include

classX

{

protected:

inti,j;

public:

X(inti,intj){X:

:

i=i,X:

:

j=j;}

voidprint(){cout<<"i="<

};

classY:

publicX

{

intk;

public:

Y(inti,intj):

X(i,j){k=i*j;}//初始化列表

voidprint()

{cout<<"i="<

};

classZ:

Y

{

public:

Z(inti,intj):

Y(i,j){}//初始化列表

voidprint()

{Y:

:

print();

//因cout<

//error'k':

cannotaccessprivatemember

//declaredinclass'Y'

//所以只能用Y:

:

print()

//详见本章§4.2“派生类成员函数和其它函数

//访问基类成员时的权限(访问控制表)”

}

};

voidmain()

{

Xobjx(5,10);

Yobjy(12,24);

Zobjz(25,50);

cout<<"X对象:

";

objx.print();

cout<<"Y对象:

";

objy.print();

cout<<"Z对象:

";

objz.print();

}

/*Results:

X对象:

i=5,j=10

Y对象:

i=12,j=24,k=288

Z对象:

i=25,j=50,k=1250*/

按照支配规则,以上每个类对象调用print()函数时,都只调用本类的函数。

请参照下图。

DAG

x(i,j)

public参

y(i,j)

private

z(i,j)

栈区存储内容

classx的对象objx

objx.i=5

objx.j=10

 

classy的对象objyclassz的对象objz

objz.x:

:

i=25

 

objy.x:

:

i=12

objz.x:

:

j=50

objy.x:

:

j=24

objz.y:

:

k=1250

objy.k=288

 

[例2]见以下本章§4.4.1中[例3]

[例3]再看一下支配规则的例子:

//acc_mod_2_2.cpp

#include

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

#include

classbase{

intx1;

public:

base(inta){x1=a;}

voidshow(){cout<

};

classderive:

publicbase{

intx2;

public:

derive(inta,intb):

base(a)

{x2=b;}

};

classgrand:

publicderive{

intx3;

public:

grand(inta,intb,intc):

derive(a,b)

{x3=c;}

};

voidmain()

{

basep(7);//

(1)

deriveder(5,10);//

(2)

grandgr(3,6,9);//(3)

p.show();//(4)

p=der;//(5)派生类对象向基类对象赋值

p.show();//(6)

p=gr;//(7)更下一级派生类对象向基类对象越级赋值

p.show();//(8)

//但反过来不行//der=p;

//error:

binary'=':

nooperatordefinedwhichtakesaright-handoperand

//oftype'classbase'(orthereisnoacceptableconversion)

}

/*Results:

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

gr.base:

:

x1=3

p.x1=7

der.base:

:

x1=5

gr.derive:

:

x2=6

der.x2=10

gr.x3=9

主函数第五句“p=der;”运行后

对象p对象der

der.base:

:

x1=5

p.x1=5

der.x2=10

 

主函数第七句“p=gr;”运行后

对象p对象gr

p.x1=3

gr.base:

:

x1=3

gr.derive:

:

x2=6

gr.x3=9

 

§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

#include

classB

{

inti;//private

protected:

intj;

public:

B(inta,intb,intc){i=a;j=b;k=c;}

voidf(){cout<

intk;

};

voidmain()

{

Bobj(1,2,3);

//cout<

//cout<

cout<

obj.f();

}

/*Results:

3

123*/

其中classB的成员f()能够访问所有成员i,j,k,但主程序只能访问classB对象中的公有成员k和f()。

可将此访问权限列于以下表内。

TABLE1(classB的访问控制表)

classname

public

protected

private

classB

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

#include

classB

{intib;

protected:

intjb;

public:

intkb;

B(inti=1,intj=2,intk=3){ib=i;jb=j;kb=k;}

};

classD:

publicB//公有继承

{

public:

voidfun_d()

{cout<<"fun_d()inclassDreadsjb="<

//cout<

//error:

'ib':

cannotaccessprivatememberdeclaredinclass'B'

//只能访问基类中保护和公有成员

}

};

classG:

publicD//公有继承的更下一层派生类

{

public:

voidfun_g()

{cout<<"fun_g()inclassGreadsjb="<

};

voidmain()

{

Dobjd;

objd.fun_d();

cout<<"main()readsobjd.kb="<

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

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1