面向对象程序设计辅导四.docx

上传人:b****5 文档编号:26438505 上传时间:2023-06-19 格式:DOCX 页数:33 大小:38.27KB
下载 相关 举报
面向对象程序设计辅导四.docx_第1页
第1页 / 共33页
面向对象程序设计辅导四.docx_第2页
第2页 / 共33页
面向对象程序设计辅导四.docx_第3页
第3页 / 共33页
面向对象程序设计辅导四.docx_第4页
第4页 / 共33页
面向对象程序设计辅导四.docx_第5页
第5页 / 共33页
点击查看更多>>
下载资源
资源描述

面向对象程序设计辅导四.docx

《面向对象程序设计辅导四.docx》由会员分享,可在线阅读,更多相关《面向对象程序设计辅导四.docx(33页珍藏版)》请在冰豆网上搜索。

面向对象程序设计辅导四.docx

面向对象程序设计辅导四

面向对象程序设计辅导(四)

---类的继承与多态性

徐孝凯

一、类的继承

类是一种抽象数据类型,是对具有共同属性和行为的对象(事物)的抽象描述。

但通常为了处理问题的方便,对事物按层进行分解,使得处于顶层(上层)的抽象事物具有处于底层(下层)抽象事物的共同特征,而处于底层的抽象事物除了具有顶层抽象事物的所有特征外,还具有本身所专有的特征。

例如对于建筑物来说,它有施工单位、竣工日期等特征;而建筑物又可细分为房屋、桥梁和纪念塔等三类,它们除了具有建筑物的共同特征外,还各自具有自己的特征,如房屋有建筑面积,桥梁有建筑高度、宽度和长度,纪念塔有塔高和形状等特征;房屋又可细分为平房和楼房两类,平房和楼房除了具有房屋的共同特征外,还具有自己的特征,如平房有庭院面积,楼房有楼层数和电梯数等特征;楼房又可细分为办公楼和居民楼两类,它们除了具有楼房的公共特征外,办公楼还具有值班电话,居民楼还具有居民户数和居住人数等特征。

可用图9-1表示它们之间的层次关系。

建筑物

房屋桥梁纪念塔

 

平房楼房

 

办公楼居民楼

图1建筑物类层次图

在C++中允许定义类之间的继承关系。

当一个类继承另一个类时,这个类被称为继承类、派生类或子类,另一个类被称为被继承类、基类或父类。

子类能够继承父类的全部特征,包括所有的数据成员和成员函数,并且子类还能够定义父类所没有的、属于自己的特征,即自己的数据成员和成员函数。

通过类的继承关系,使得一些类的代码可以为定义另一些类所重用,避免了代码的重新书写和调试,能够开发出便于维护和扩充、可靠性高的软件。

所以,类的继承是软件开发中的一项重要技术。

1.派生类定义的格式

class<派生类名>:

<基类表>{<成员表>};

它同一般类的定义格式大体相同,只是在类名和左花括号之间增添了一个冒号和一个基类表。

语句定义中的派生类名是新定义的类类型标识符,它是基类表中所给基类的一个派生类;基类表中包含有一个或多个用逗号分开的类项,每个类项为一个已被定义的作为基类使用的类名,它前面可以带有继承权限指明符用以规定被继承的权限;花括号内的成员表是为该派生类定义的数据成员和成员函数的列表。

基类表中每个基类名前面可以使用的继承权限指明符仍为类成员表中为规定成员访问权限所使用的指明符public,private或protected,它们分别表示派生类公用(公有)、私有或保护继承该基类。

若一个基类名前没有使用任一指明符也是允许的,对于class定义语句来说,隐含为private指明符,即派生类私有继承该基类,对于struct定义语句来说,隐含为public指明符,即派生类公用继承该基类,这种规定与定义它们的成员时缺省的访问权限的规定完全相同。

当一个基类被派生类公用继承时,则基类中的所有public成员也同时成为派生类中的public成员,基类中的所有protected成员也同时成为派生类中的protected成员,基类中的所有private成员不转换为派生类中的任何成员,仍作为基类的私有成员保留在基类中,也可以说同时保留在派生类中,因为派生类继承了基类中的所有成员。

由于基类的私有成员没有同时成为派生类中的成员,所以派生类的成员函数无法直接访问它们,只能通过基类提供的公用或保护成员函数来间接访问。

当然,若把派生类定义为基类的友元,则可直接访问其私有成员。

当一个基类被派生类私有继承时,则基类中的所有public成员和所有protected成员将同时成为派生类中的private成员,基类中的所有private成员仍只作为基类的私有成员存在,不转换为派生类中的任何成员。

当一个基类被派生类保护继承时,则基类中的所有public成员和所有protected成员将同时成为派生类中的protected成员,基类中的所有private成员同上述两种继承一样,仍只能作为基类的私有成员存在,不是派生类的成员。

无论任何一个类,无论它的成员是靠继承而来的,还是自己定义的,都属于自己的成员,该类的成员函数能够访问该类中具有任何访问权限的成员,同时也能够访问其他类中具有公用访问权限的成员和类外的对象与函数,不能访问其他类中的保护成员和私有成员,即使其他类是自己继承的类,或自己成员所属的类也是如此。

在一个派生类中,其成员由两部分组成,一部分是从基类继承得到的,另一部分是自己定义的新成员,所有这些成员也分为公用(public)、私有(private)和保护(protected)这三种访问属性。

另外,从基类继承下来的全部成员构成派生类的基类部分,此部分的私有成员是派生类不能直接访问的,其公用和保护成员是派生类可以直接访问的,因为它们已同时成为了派生类中的成员,但在派生类中的访问属性可能有改变,视对基类的继承权限而定。

带有一个基类的派生类的构成如图2所示。

私有成员

基类部分保护成员

派生类公共成员

私有成员派生类成员

新定义部分保护成员

公用成员

图2派生类构成示意图

当派生类公用继承基类时,派生类中的公用成员包括基类部分的公用成员和新定义部分的公用成员,保护成员包括基类部分的保护成员和新定义部分的保护成员,私有成员仅为新定义部分的私有成员。

当派生类保护继承基类时,派生类中的公用成员仅为新定义部分的公用成员,保护成员包括基类部分的公用成员和保护成员以及新定义部分的保护成员,私有成员仅为新定义部分的私有成员。

当派生类私有继承基类时,派生类中的公用成员仅为新定义部分的公用成员,保护成员也仅为新定义部分的保护成员,私有成员包括基类部分的公用成员和保护成员以及新定义部分的私有成员。

每个派生类对象所占有的存储空间的大小等于其基类部分的所有数据成员占有的存储空间的大小与新定义部分的所有数据成员占有的存储空间大小的总和,并且前面的存储空间分配给基类部分的数据成员使用,后面的存储空间分配给新定义部分的数据成员使用。

2.格式举例

(1)structX{

inta;

};

structY:

X{

intb;

intc;

};

这里X和Y均为结构,所以它们的成员缺省为公用访问属性,Y对X的继承也缺省为公用继承。

派生类Y中包含有三个公用成员a,b和c,其中a又同时为基类X部分的公用成员。

由于Y中的成员都是公用的,所以外部函数可以直接访问以Y为类型的对象中的任何成员。

每个Y类型的对象具有12个字节的存储空间,其中前4个字节存储数据成员a,接着4个字节存储数据成员b,最后4个字节存储数据成员c。

假定y是Y结构类型的一个对象,则y.a,y.b和y.c都是有效的表示,利用它们可以访问y中的指定成员。

当然y.a也可以表示为y.X:

:

a,因为a同时又是y结构中的基结构类型X中的成员。

(2)classA{

inta1;

protected:

inta2;

public:

inta3;

A(){a1=a2=a3=0;}

A(intx1,intx2,intx3):

a1(x1),a2(x2),a3(x3){}

voidOutA(){cout<

intGeta1(){returna1;}

};

classB:

publicA{

intb1;

protected:

intb2;

public:

intb3;

B(){b1=b2=b3=0;}

B(intx1,intx2,intx3):

A(x1,x2,x3){

b1=x1+1;b2=x2+2;b3=x3+3;

}

voidOutB(){

A:

:

OutA();

cout<

}

intSum(){

returnGeta1()*b1+a2*b2+a3*b3;

}

};

voidmain()

{

Bb1,b2(1,2,3);

b1.OutB();

b2.OutB();

cout<

cout<

};

在这个例子中,定义了两个类A和B,并且把B定义为A的派生类,则A是B的基类,B公用继承了A中的所有成员。

在类A中定义有三个整数成员a1,a2和a3,它们的访问权限依次为私有、保护和公用;定义有一个无参构造函数,它对所有数据成员赋初值为0;定义有一个带参构造函数,它对三个数据成员分别赋初值为形参x1,x2和x3的值;定义有一个OutA公用成员函数,用于输出所有数据成员的值;定义有一个Geta公用成员函数,用以返回私有成员a1的值。

在类B中除了继承类A中的所有成员外,还定义有三个整数成员b1,b1和b3,它们的访问权限也相应为私有、保护和公用,以及定义有四个公用成员函数。

第一个为无参构造函数,执行时首先隐含调用基类A的无参构造函数给属于基类A中的数据成员初始化,接着执行函数体给新定义的数据成员初始化为0。

第二个为带参构造函数,它首先利用初值项A(x1,x2,x3)调用基类A的带参构造函数,对属于A中的数据成员初始化,接着执行函数体对新定义的数据成员初始化。

第三个为OutB函数,它首先调用OutA()函数输出基类A中数据成员的值,然后输出B类中新定义的所有数据成员的值。

由于OutA()函数既是基类A部分的公用函数,又是B类继承过来的公用函数,所以写成OutA()或A:

:

OutA()的调用形式完全相同。

在派生类的成员函数中,使用基类名和类区分符作为成员名的前缀能够访问既属于基类又属于派生类的成员。

第四个为Sum函数,它返回a1*b1+a2*b2+a3*b3的值,由于a1是基类A的私有成员,类A外的函数无法访问,所以只能由类A提供的成员函数Geta1()得到它的值,因此在返回表达式中不能直接书写为a1,而应书写为Geta1()。

在派生类的构造函数中,对属于基类的成员进行初始化是通过在初值表中给出具有“基类名(实参表)”格式的初值项调用基类的构造函数来实现的。

若初值表中没有给出调用基类构造函数的初值项,则自动调用基类的无参构造函数进行初始化,就如同在初值表中使用了“基类名()”初值项进行调用一样。

在一个派生类中同时含有类成员时,则类成员的初始化和基类成员的初始化一样,都必须在构造函数的初值表中给出初值项,以此调用相应的构造函数来实现,当省略初值项时则调用相应的无参构造函数来实现。

派生类构造函数的执行顺序是:

首先调用基类构造函数实现对基类成员的初始化,接着调用成员所属类的构造函数实现类成员的初始化,最后实现对新定义的非类成员的初始化,最后一步的初始化可以通过初值表,也可以通过函数体进行。

此例中主函数定义了派生类B的两个对象b1和b2,b1对象通过调用无参构造函数进行初始化,使得所有数据成员的值均为0,b2对象通过调用带参构造函数进行初始化,使得b2中属于基类A部分的数据成员a1,a2和a3被初始化为1,2和3,属于新定义部分的数据成员b1,b2和b3被初始化为2,4和6,当然属于基类A部分的a2和a3也同时为b2的保护成员和公用成员。

该程序的运行结果如下:

000

000

123

246

028

24

(3)classAA{

protected:

inta;

public:

AA(intx=0):

a(x){}

intGeta(){

returna;

}

};

classBB{

protected:

intb;

public:

BB(intx=0):

b(x){}

};

classCC:

publicAA,publicBB{

intc;

AAd;

public:

CC(){c=0;}

CC(intx,inty,intz):

AA(x),BB(y),

c(z),d(x+y+z)

{}

voidOutCC(){

cout<<"a="<

cout<<",c="<

cout<<"d.a="<

}

};

voidmain()

{

CCc1,c2(3,4,5);

c1.OutCC();

c2.OutCC();

cout<

}

在这个例子中定义了三个类,其中AA和BB均为CC的基类,派生类CC既继承了AA的全部成员又继承了BB的全部成员,称CC为多继承的派生类,相反当只有一个基类时则称为单继承。

在类AA和BB的构造函数中,其参数都带有缺省值,所以它们既可以作为无参构造函数接受无参调用,又可以作为带参构造函数接受带参调用。

在执行类CC的无参或带参构造函数时,无论初值表中各初值项的前后次序如何,都是首先调用类AA的构造函数对CC类中属于AA类的成员初始化,接着调用类BB的构造函数,对CC类中属于BB类的成员初始化,再接着调用类AA的构造函数对类数据成员d进行初始化,最后初始化数据成员c。

在执行类CC中的成员函数OutCC()时,输出保护数据成员a和b的值,私有数据成员c的值,以及私有数据成员d中的a成员的值。

由于d.a是d所属类AA的保护成员,类AA外不能够直接访问,所以若写成d.a进行访问是错误的,只能采用类AA提供的公共成员函数Geta()读取它的值。

在主函数中定义了类CC的两个对象c1和c2。

当c1自动调用类CC的无参构造函数进行初始化时,首先调用AA的无参构造函数对c1.a初始化为0,接着调用BB的无参构造函数对c1.b初始化为0,再接着调用AA类的无参构造函数对c1.d.a初始化为0,最后执行函数体对c1.c初始化为0。

当c2自动调用类CC的带参构造函数时,首先把实参值3,4和5分别赋给形参x,y和z,接着依次通过初值表中给出的初始项AA(x),BB(y)和d(x+y+z)调用对应类的构造函数,分别给c2.a,c2.b和c2.d.a赋初值为3,4和12,最后执行初值项c(z)的操作给c2.c赋初值为5。

该程序运行结果为:

a=0,b=0,c=0

d.a=0

a=3,b=4,c=5

d.a=12

16

(4)classA1{

protected:

inta;

public:

A1(intx=0){a=x;}

intSquare(){returna*a;}

};

classA2:

publicA1{

public:

intb;

A2(intaa=0,intbb=0):

A1(aa){

b=bb;

}

intSquare(){returnb*b;}

};

classA3:

publicA2{

chara;

intb;

intc;

public:

A3(){a='\0';b=0;c=0;}

A3(charch,intx):

A2(x,3*x){

a=ch;

b=2*x;

c=4+x;

}

intSquare(){return(b+c)*(b+c);}

voidOutA3(){

cout<<"a="<<'\''<

cout<<",c="<

cout<<"A2:

:

b="<

:

b<

cout<<"A1:

:

a="<

:

a<

:

a

cout<

:

Square()<<'';

cout<

:

Square()<

}

};

voidmain()

{

A3d1,d2('d',5);

cout<

cout<

:

Square()<<''<

:

Square()<

cout<

d1.OutA3();

cout<

d2.OutA3();

}

在这个例子中,A2公用继承了A1,A3又公用继承了A2,所以A3包含有在这三个类中定义的所有成员。

在派生类中定义的成员可以与基类或基类的基类中定义的成员具有相同或不同的名字,若不同时,成员名能够唯一确定所处的位置,不必加类名和类区分符作为前缀限定;若相同时,对于不加类名和类区分符作为前缀的成员名,则表示当前类中定义的成员,若要访问与派生类同名的基类中的成员,则必须在成员名前加上基类名和类区分符。

如在三个类A1,A2和A3中都定义有成员函数Square,并且都是它们的公用成员,由于继承关系,它们都是A3中的公用成员。

在A3的成员函数中使用Square()表示调用该类中定义的成员函数,使用A2:

:

Square()表示调用基类A2中定义的成员函数,使用A1:

:

Square()表示调用间接基类A1(它是A2的直接基类,是A3的间接基类,又称传递基类)中定义的成员函数。

又如在A3和间接基类A1中都定义有数据成员a,并且分别是各自的私有成员和保护成员,由于继承关系,A1中定义的a同时成为A3中的保护成员,允许在A3的成员函数中使用,当成员名a前不加基类名和类区分符时则表示访问在当前类即A3中定义的私有成员a,当成员名a前加上基类名A1和类区分符时则表示访问在基类A1中定义的保护成员a。

在类A3中包含有无参和带参两个构造函数,当定义的A3类的对象调用任一构造函数执行时,都首先调用基类A2的构造函数,此时又首先调用A2的基类A1的构造函数,A1的构造函数执行结束后返回到A2的构造函数执行,待A2的构造函数执行结束后,又返回到A3的构造函数执行。

所以执行一个类的构造函数时,最深(即最顶)层的基类构造函数最先执行,接着执行次深层的基类的构造函数,依次向下,最后执行当前类的构造函数对新定义的成员初始化。

当然若不含有任何基类,则直接执行当前类的构造函数。

此程序的输出结果如下,请同学们结合主函数中的语句自行分析输出结果。

0361

22525

20

a='',b=0,c=0

A2:

:

b=0

A1:

:

a=0

000

a='d',b=10,c=9

A2:

:

b=15

A1:

:

a=5

36122525

(5)#include

#include

classB1{

char*a;

protected:

B1():

a(0){}

public:

B1(){delete[]a;cout<<"B1";}

voidOutput(){cout<

voidSeta(char*aa){

delete[]a;

a=newchar[strlen(aa)+1];

strcpy(a,aa);

}

};

classB2:

publicB1{

char*b;

public:

B2():

b(0){}

B2(){delete[]b;cout<<"B2";}

voidOutput(){

B1:

:

Output();

cout<

}

voidSetb(char*aa,char*bb){

Seta(aa);

delete[]b;

b=newchar[strlen(bb)+1];

strcpy(b,bb);

}

};

voidInput(B2*&r,intn)

{

r=newB2[n];

chara[20],b[20];

for(inti=0;i

cout<<"Inputtwostrings:

";

cin>>a>>b;

r[i].Setb(a,b);

}

}

voidmain()

{

B2*a=NULL;

intn=3;

Input(a,n);

for(inti=0;i

a[i].Output();

delete[]a;

cout<

}

在这个例子中包含有B1和B2两个类,每个类只含有一个数据成员,并且均为私有的字符指针。

在B1中把无参函数定义为保护成员,它只能由本类及派生类调用,其他函数不能调用。

在B1类中定义有三个公用成员函数,一个为析构函数,用以删除a所指向的动态字符空间;第二个为输出函数,用以输出a所指向的字符串;第三个为给a赋值的函数,它首先释放a所指向的存储空间,接着根据形参字符串的长度为a动态分配所指向的存储空间,最后把形参字符串拷贝到a所指向的存储空间中。

在B2类中,定义有四个公用成员函数,一个为无参构造函数,它给字符指针b赋初值0,即置空;第二个为析构函数,它删除b所指向的动态存储空间;第三个为输出函数,用以分别输出a和b指针所指向的字符串,由于a是基类B1的私有成员,在B2中不能直接访问,只能通过调用B1提供的输出函数Output输出a字符串,又由于B2中也定义有Output函数,所以在调用B1中的这个函数时必须在函数名前加上B1和类区分符;第四个为给a和b赋值的函数,它首先调用Seta函数(因其名字在B2类中唯一,所以加不加B1和类区分符限定均可)把aa字符串赋给a指针所指向的动态字符空间,接着把bb字符串赋给b指针所指向的动态字符空间。

程序中包含有一个Input函数,该函数中的第1条语句分配具有n个元素的类型为B2的动态数组,并将其首地址赋给引用形参r,该动态数组的每一个元素将由自动调用的B2类的无参构造函数初始化,当然调用该构造函数时又将首先调用基类B1的无参构造函数,初始化属于基类的成员。

第2条语句定义两个字符数组a和b,用来保存下面循环中从键盘上输入的字符串。

第3条语句使循环体执行n次,每次从键盘的输入中顺序提取两个字符串并分别赋给a和b,然后把它们作为实参用数组r中的相应元素去调用Setb成员函数,给该元素中的属于基类的私有成员a和在派生类中定义的私有成员b赋值。

由于该函数使用的参数r为引用,所以在函数体中对r的赋值实际上是对调用该函数的对应实参的赋值,即利用实参可以得到在这个函数中动态分配的数组空间的首地址和数组中每个元素的值。

在主函数中定义有一个B2类的指针对象a并赋初值为空,用这个指针a和整型对象n作为实参去调用Input函数,该函数完成对动态分配的具有n个元素的a数组赋值的任务,接着主函数通过执行for循环输出a数组中的每个元素的值,然后使用delete语句删除(释放)指针a所指向的动态数组空间,由于动态数组中的每个元素为B2类对象,所以在释放前对于每一个元素都将自动调用B2类的析构函数,删除b指针所指向的动态字符串空间,又由于B2类包含有基类B1,所以在执行B2类的析构函数的函数体之后,待返回之前,将自动调用基类B1的析构函数,删除a指针所指向的动态字符串空间,然后由基类B1的析构函数返回到B2的析构函数,再由

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

当前位置:首页 > 表格模板 > 表格类模板

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

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