类的继承与多态性_第1页
类的继承与多态性_第2页
类的继承与多态性_第3页
类的继承与多态性_第4页
类的继承与多态性_第5页
已阅读5页,还剩49页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

类的继承与多态性演示文稿当前1页,总共58页。(优选)类的继承与多态性当前2页,总共58页。一、继承的概念继承是软件重用的一种方式。通过继承建立新类的优越性:通过重用已有代码,提高编程效率,降低软件开发成本;更有效地保持共有特性的一致性;提高了系统的可维护性;是实现多态性的基础。被继承的类称为“父类”或“基类”;继承者被称为“子类”或“派生类”;§9.1类的继承当前3页,总共58页。继承体现了类的“层次”,从层次上来说,就是位于上层的是基类,下层的为派生类;派生类吸收了基类的非private成员,并增添新的性能。基类、派生类是相对的。当前4页,总共58页。基类与派生类示例基类派生类学生本科生,研究生形状圆,三角形,矩形,球体,立方体贷款汽车贷款,住房贷款,抵押贷款雇员教职员工,后勤人员账户支票账户,储蓄账户一般地,派生类的对象都“是”其基类的一个对象;基类所能表示的对象集合比派生类所表示的对象集合大.eg.矩形类“是”四边形类,反之不成立.当前5页,总共58页。单继承建筑物房屋桥梁纪念塔平房楼房居民楼办公楼单继承:派生类仅由一个基类派生而来当前6页,总共58页。多继承EmployeeFacultyStaffAdministratorTeacherAdministratorteacher多重继承:派生类由多于一个的基类派生而成多级继承:等于或多于两个的继承层次当前7页,总共58页。二、派生类的定义定义格式:

class<派生类名>:<继承方式><基类名>

{派生类新定义成员};继承方式包括:

public/protected/private

分别表示派生类公有继承基类,

派生类保护继承基类,

派生类私有继承基类.classA{pulbic:A(intaa=0){a=aa;}voidshowA();private:inta;};classB:publicA{public:B(intbb=0){b=bb;}private:intb;};当前8页,总共58页。聚合VS继承聚合:B类对象的成员包括A类的一个或多个对象。继承:B类对象的成员包括A类的成员。classA{pulbic:A(intaa=0){a=aa;}voidshowA();private:inta;};classB:publicA{public:B(intbb=0){b=bb;}private:intb;};classA{pulbic:A(intaa=0){a=aa;}voidshowA();private:inta;};classB{public:B(intbb=0):aa(0){b=bb;}private:intb;

Aaa;};B-b:int+showA(),+B()B-b:int,-aa:A+B()当前9页,总共58页。派生类成员的构成后举例基类中的私有成员不能被派生类成员直接引用。派生类对象先存储其基类子对象部分,再存储新增部分。当前10页,总共58页。派生类成员和外部对象访问基类中的私有成员派生类专属成员使用基类私有成员的方式:①不能直接使用基类中的私有成员;②成员函数方式:派生类成员函数需要通过基类提供的公有或保护成员函数间接访问;外部对象通过基类的公有成员函数使用其私有成员;③友元方式:把派生类定义为基类的友元,则派生类成员可以直接使用基类成员;而派生类对象则可以用友元函数间接使用基类私有成员.当前11页,总共58页。派生类的三种继承方式公有继承:除基类的私有成员继续为基类的私有成员外,基类的公有成员和保护成员分别成为派生类的公有成员和保护成员。私有继承:除基类的私有成员继续为基类的私有成员外,基类的公有成员和保护成员将成为派生类的私有成员。保护继承:除基类的私有成员继续为基类的私有成员外,基类的公有成员和保护成员将成为派生类的保护成员。当前12页,总共58页。继承方式基类成员public方式派生类内/类外对象private方式派生类内/类外对象protected方式派生类内/类外对象private不可见不可见不可见protected可见/不可见可见/不可见可见/不可见public可见可见/不可见可见/不可见三种继承方式存取权限的关系类内:指派生类内的成员函数类外:指类外部定义的派生类对象可见:即可以直接访问(使用)不可见:只能间接访问当前13页,总共58页。继承成员的访问属性---举例示例

classX{private:inta;protected:intb;public:intc;};classY1:publicX{intd;public:

output(){returnd;}};Y1的构成-----

私有:d

保护:b

公有:output(),c不可直接访问:a(仍属于基类)当前14页,总共58页。继承成员的访问属性---举例示例classX{private:inta;protected:intb;public:intc;};classY2:protectedX{intd;public:

output(){returnd;}};Y2的构成----

私有:d

保护:bc

公有:output()

不可直接访问:a(仍属于基类)当前15页,总共58页。继承成员的访问属性---举例示例classX{private:inta;protected:intb;public:intc;};classY3:private

X{intd;public:

output(){returnd;}};Y3的构成----

私有:dbc

保护:公有:output()

不可直接访问:a(仍属于基类)当前16页,总共58页。(1)public继承方式在public继承方式下,派生类内的成员函数中可以访问基类和派生类中的保护成员,但外部程序中定义的派生类对象不可访问基类和派生类中的保护成员。Eg.例题“public继承”在public继承方式下,派生类内的成员函数中可以访问基类和派生类中的保护成员,但外部程序中定义的派生类对象不可访问基类和派生类中的保护成员。Eg.例题“private继承”(2)private继承方式当前17页,总共58页。Eg.例题“public继承”#include<isotream.h>classA{private:inta1;protected:inta2;public:inta3;A(intx1,intx2,intx3):A(x1),a2(x2),a3(x3){}~A(){}};classB:publicA{private:intb1;protected:intb2;public:intb3;B(intx1,intx2,intx3,inty1,inty2,inty3):A(x1,x2,x3),b1(y1),b2(y2),b3(y3){}~B(){}voidprint()const;};voidB::print()const{//cout<<“a1=”<<a1<<endl;//非法

cout<<“a2=”<<a2<<endl;cout<<“a3=”<<a3<<endl;cout<<“b1=”<<b1<<endl;cout<<“b2=”<<b2<<endl;cout<<“b3=”<<b3<<endl;}voidshow(B&myB){//cout<<myB.a1<<myB.a2<<endl;//非法

cout<<“a3=”<<myB.a3<<endl;//cout<<myB.b1<<myB.b2<<endl;//非法

cout<<“b3=”<<myB.b3<<endl;当前18页,总共58页。Eg.例题“private继承”#include<isotream.h>classA{private:inta1;protected:inta2;public:inta3;A(intx1,intx2,intx3):A(x1),a2(x2),a3(x3){}~A(){}};classB:privateA{private:intb1;protected:intb2;public:intb3;B(intx1,intx2,intx3,inty1,inty2,inty3):A(x1,x2,x3),b1(y1),b2(y2),b3(y3){}~B(){}voidprint();};voidB::print(){//cout<<“a1=”<<a1<<endl;//非法

cout<<“a2=”<<a2<<“a3=”<<a3<<endl;cout<<“b1=”<<b1<<“b2=”<<b2<<endl;cout<<“b3=”<<b3<<endl;}voidshow(AmyA,B&myB){//cout<<myA.a1<<myA.a2;非法

cout<<myA.a3<<endl;//cout<<“myB.a1<<myB.a2<<endl;//非法

//cout<<“a3=”<<myB.a3<<endl;//非法

//cout<<myB.b1<<myB.b2<<endl;//非法

cout<<“b3=”<<myB.b3<<endl;当前19页,总共58页。三、派生类的构造函数继承关系中,基类的两个特殊成员函数---构造函数和析构函数不能被继承。因此,派生类的构造函数不仅要初始化派生类中定义的数据成员,而且要初始化派生类中的基类子对象(派生类对象中由基类定义的部分称作基类子对象)。基类子对象的初始化赋值是通过在派生类构造函数中显式或隐式地调用基类构造函数实现的。构造函数的执行顺序:先执行基类构造函数,再执行派生类构造函数。基类子对象派生类专有部分派生类当前20页,总共58页。例1:P267例题classA

{

inta1;

protected:

inta2;

public:

inta3;

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

A(intx1,intx2,intx3):a1(x1),a2(x2),a3(x3){}

voidout(){cout<<a1<<‘‘<<a2<<‘‘<<a3<<endl;}

intGeta1(){returna1;}

};返回B类当前21页,总共58页。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;}

voidout(){A::out();

cout<<b1<<‘‘<<b2<<‘‘<<b3<<endl;}

intSum(){returnGeta1()*b1+a2*b2+a3*b3;}};隐含调用A()显式调用

A(int,int,int)调用类A中的无参构造函数返回main()函数//A类和B类中都有Out(),则用::界定其所属,若不加则指的是本类成员.调用类A中的带参构造函数当前22页,总共58页。

voidmain(){

Bb1,b2(1,2,3);b1.Out();b2.Out();cout<<b1.Sum()<<‘‘<<b2.Sum()<<endl;cout<<sizeof(B)<<endl;}调用类B中的构造函数初始化对象当前23页,总共58页。四、派生类的析构函数撤销派生类对象时,系统先自动调用派生类的析构函数,再调用基类的析构函数。系统对派生类和基类析构函数的自动调用顺序和其对构造函数的调用顺序正好相反。例:类层次中普通析构函数当前24页,总共58页。五、基类和派生类的赋值兼容规则在满足公有继承的限制条件下,C++允许派生类对象到基类对象的自动转换,通常称为赋值兼容规则。规则1:可以用派生类对象为基类对象赋值规则2:可以用派生类对象初始化基类的引用对象规则3:可以用基类的指针指向派生类对象eg.赋值兼容规则eg.类层次中普通析构函数当前25页,总共58页。§9.2多重继承多级继承引例:Windows风格用户界面设计先设计出窗口类、尺寸类、横向/纵向滚动条类、按钮类;用户界面类将上述各类作为基类,通过多重继承产生。一、多重继承时的派生类

1、定义格式:

class派生类名:

[继承方式1][基类名1],[继承方式2][基类名2],…{派生类成员定义};注:各基类名不能相同当前26页,总共58页。2、初始化数据成员—调用构造函数1)调用顺序构造函数调用顺序:

(1)初始化第一个基类子对象,即调用第一个基类的构造函数;初始化第二个基类子对象,即调用第二个基类的构造函数;

……

初始化第n个基类子对象,即调用第n个基类的构造函数。

(2)派生类中若有基类对象作成员,则调用相应基类的构造函数。

(3)最后初始化派生类普通成员。析构函数调用顺序:与构造函数相反。当前27页,总共58页。例:多重继承时的构造函数的调用P269说明:派生类初始化成员的顺序:

首先对基类子对象成员的初始化,

然后对新增对象成员初始化,

最后对新增非对象成员初始化.注:前两步必须在构造函数的初值表中给出初值项

(省略时调用无参构造函数);

后一步可以通过初值表,也可以通过函数体进行.当前28页,总共58页。二、多级继承中的派生类

classA{};classB:publicA{};classC:publicB{};B直接继承类A,C直接继承类B,间接继承类A。eg.P270多级继承举例多级继承时的初始化顺序:先执行最上层基类的构造函数,依次向下执行各基类的构造函数,最后是当前派生类的构造函数对新增成员的初始化.当前29页,总共58页。执行C的构造函数前先调用B1、B2构造函数;执行B1、B2构造函数前先分别调用A的构造函数。AB1B2CA为C的公共基类eg.数据成员二义性三、混合多继承中的构造函数classC:publicB1,publicB2{};当前30页,总共58页。1、数据成员二义性假设图中类A有数据成员a,则C中有两个a成员,a是哪个派生类的成员?产生了二义性。解决方法:

(1)加类名分辨符B1::或B2::(2)定义虚基类

eg1.数据成员二义性A(a)B1(a,b1)B2(a,b2)C(a,b1,b2,c)四、多继承中二义性问题的解决当前31页,总共58页。2、函数成员二义性

B1,B2中都含有print()成员函数,C继承了B1,B2,则C中有两个print()函数。解决方法:加类区分符::eg2.函数成员二义性B1(print())B2(print())C当前32页,总共58页。§9.3虚基类消除数据成员二义性的两种方法的区别:

(1)”类名::”法:消除二义性同时实现程序设计的多态性,但在派生类对象中存在多个公共基类的同名数据成员

(2)”虚函数”法:消除二义性,且在派生类对象中只存在一个公共基类的数据成员设计虚基类:

(1)一个基类有多于一个派生类时,在这些派生类“继承方式”前加上

virtual关键字。

classB1:virtualpublicAclassB2:virtualpublicA

此时将公共基类A称为虚基类。AB1B2C当前33页,总共58页。(2)虚基类子对象由最新派生出来的派生类直接初始化。

B1,B2对类A子对象初始化,

C也要对类A子对象初始化。

eg.虚基类注意:对非虚基类,不能在C中对A类子对象初始化。AB1B2C当前34页,总共58页。§9.4类的虚函数和多态性对于相同的函数调用,依赖每个对象自己做出恰当的响应,这是多态性的关键思想。多态性(polymorphism):不同对象在接收同样的消息时,所做出的响应不同。多态性的实现依赖于动态联编。指调用名称相同的成员函数指不同派生类的成员函数当前35页,总共58页。一、滞后联编

联编:指一个标识符名和一个存储地址联系在一起的过程。

1、静态联编(早期联编)指:联编工作出现在编译连接阶段,因为这种联编过程在程序开始运行之前完成,所以又称早期联编。在编译时就解决了程序中的操作调用与执行该操作代码间的关系,确定这种关系又被称为束定,因此,静态联编又称为静态束定。

2、动态联编(滞后联编)指:在程序执行时进行的联编,称动态联编,或称动态束定,又叫滞后联编。动态联编时在不同阶段被束定的类的对象将是不同的。动态联编的实现:C++中,在虚函数的支持下实现。当前36页,总共58页。二、虚函数的定义1、定义:virtual成员函数原型;2、注意:

(1)只有在类的继承关系中才存在虚函数。在基类中声明某成员函数为虚函数,则派生类中(名字、参数个数、参数类型和返回值类型都)相同的成员函数都视作虚函数。

(2)使用虚函数须利用赋值兼容规则,要求public继承方式。

(3)多态性使我们编写的程序在处理同一个类层次结构下类的对象时好像它们是基类的所有对象一样。当前37页,总共58页。三、虚函数的使用多态性是利用基类的指针句柄和基类的引用句柄,而不是利用名字句柄。实现多态的步骤:

(1)定义基类指针

(2)用该指针指向基类或派生类对象

(3)当程序发送虚函数消息时:若指针指向基类对象,系统调用基类成员函数,若指针指向派生类对象,系统调用派生类成员函数。

(4)若没有步骤(2)中赋值,系统按早期联编方式调用基类成员函数。当前38页,总共58页。classX1{intx;public:X1(intxx=0){x=xx;}virtualvoidOutput(){cout<<“x=“<<x<<endl;}};classY1:publicX1{inty;public:Y1(intxx=0,intyy=0):X1(xx){y=yy;}virtualvoidOutput(){X1::Output();cout<<“y=“<<y<<endl;}};可以去掉这里的virtual例:虚函数当前39页,总共58页。classZ1:publicX1{intz;public:Z1(intxx=0,intzz=0):X1(xx){z=zz;}virtualvoidOutput(){X1::Output();cout<<“z=“<<z<<endl;}};voidmain(){X1a(5);Y1b(6,7);Z1c(8,9);X1*p[3]={&a,&b,&c};for(inti=0;i<3;i++){p[i]->Output();cout<<endl;}}结论:定义了虚函数后,通过基类指针调用虚函数时,实际被调用的是指针所指对象内的函数;结果:x=5

x=6

Y=7

x=8

z=9不定义虚函数时的结果:x=5

x=6

x=8基类子对象派生类专有部分派生类当前40页,总共58页。虚函数的调用说明:使用基类指针指向其派生类的对象;相同的调用方式实际上能够调用一组中的任何一个派生类中的虚函数,即是多态性。由于被实际调用的函数是在运行时确定的,因此称为动态绑定虚函数的使用限制:只能通过指针或引用调用虚函数;如果用普通对象名调用虚函数,将釆用静态联编方式;虚函数定义时,函数名、返回类型、参数类型、个数和顺序与基类中的函数定义应完全一致。(不同于重载函数)构造函数不能定义为虚函数,但析构函数可以。当前41页,总共58页。voidmain(){Bb;

b.act2();}考虑:

1、程序结果是什么?

2、如果将A中act2()实现为:

voidA::act2(){this->act1();}

结果如何?

3、如果将A中act1()前的virtual

去掉,结果如何?例:#include<iostream.h>classA{public:

virtualvoidact1();voidact2(){act1();}};voidA::act1(){cout<<“A::act1()called.”<<endl;}classB:publicA{public:

voidact1();};voidB::act1(){cout<<“B::act1()called.”<<endl;}B::act1()called.B::act1()called.A::act1()called.可以由成员函数调用虚函数当前42页,总共58页。四、构造函数中调用虚函数#include<iostream.h>classA{public:A(){}virtualvoidf(){cout<<“A::f()called.\n”;}};classB:publicA{public:B(){f();}voidg(){f();}};classC:publicB{public:C(){}virtualvoidf(){cout<<“C::f()called.\n”;}};voidmain(){Cc;c.g();}结果:A::f()called.C::f()called.结论:构造函数中调用虚函数时,釆用静态联编,即构造函数调用的虚函数是自己类中实现的虚函数,如果自己类中没有实现这个虚函数,则调用基类中的虚函数。当前43页,总共58页。例:定义普通析构函数#include<iostream.h>classAA{inti;public:AA(intn):i(n){}~AA(){cout<<“AA“<<endl;}};classBB:publicAA{char*p;public:BB(intn,char*s):AA(n){p=newchar[strlen(s)+1];strcpy(p,s);}~BB(){deletep;cout<<“BB”<<endl;}};voidmain(){AA*p=newBB(5,”abcd”);

deletep;

};结果:AA普通析构函数在使用delete删除一个对象时,是静态联编,关联的只是基类的析构函数。五、虚析构函数当前44页,总共58页。#include<iostream.h>classAA{inti;public:AA(intn):i(n){}virtual~AA(){cout<<“AA“<<endl;}};classBB:publicAA{char*p;public:BB(intn,char*s):AA(n){p=newchar[strlen(s)+1];strcpy(p,s);}~BB(){deletep;cout<<“BB”<<endl;}};voidmain(){AA*p=newBB(5,”abcd”);

deletep;

};结果:BB

AA虚析构函数的目的在于使用delete删除一个对象时,是动态联编,关联的是子类的析构函数。例:定义虚析构函数当前45页,总共58页。六、纯虚函数与抽象类软件使用者:某软件只要用到一些具体事物的类(如:Sphere,Cylinder,Cone类)

软件设计者:增加一个更抽象的基类效果更好(代码重用)(如:Circle类)--某些成员函数在基类中的实现变得没意义(如:Area(),Volume()函数),但其声明仍有意义。

问题:能否将这样的成员函数在基类中只作声明,而其实现留给派生类呢?

用纯虚函数将基类改造为抽象类当前46页,总共58页。1、纯虚函数定义:

virtual返回类型函数名(参数表)=0纯虚函数在基类中声明,具体实现在派生类中。2、抽象类包含纯虚函数的类称为抽象类。抽象类为其所有子类提供了统一的操作界面。说明:抽象类不能实例化,即不能声明抽象类对象;抽象类只作为基类被继承,无派生类的抽象类无意义可定义指向抽象类的指针或引用,它们必然指向派生类对象,从而实现多态性。当前47页,总共58页。例:纯虚函数与抽象类classShape{public:

virtualdoublearea()=0;};classPoint{doublex,y;public:Point(doublex0=0.0,doubley0=0.0):x(x0),y(y0){}doublegetX(){returnx;}doublegetY(){returny;}};当前48页,总共58页。classRectAngle:publicShape{Pointul;//左下角顶点

Pointdr;//右上角顶点public:RectAngle(Pointp1,Pointp2):ul(p1),dr(p2){}

virtualdoublearea(){

return(dr.getX()-ul.getX())*(dr.getY()-ul.getY());

}PointgetUpleft(){returnul;}PointgetDownright(){returndr;}};当前49页,总共58页。classCircle:publicShape{Pointcen;//圆心

doublerad;//半径public:Circle(Pointc,doubler):cen(c),rad(r){}

virtualdoublearea(){return3.1415926*rad*rad;}PointgetCenter(){returncen;}doublegetRadius(){returnrad;}};voidshowArea(Shape&s){cout<<s.area();}voidmain(){RectAngle

r(Point(2.0,2.0),Point(8.0,8.0));

showArea(r);cout<<endl;Circlec(Point(8,8),4);

showArea(c);}通过调用showArea()这同一动作,计算出不同形状的面积,即同一消息,不同响应-----多态性。当前50页,总共58页。类非static数据成员函数成员对象1非static数据成员对象2非static数据成员对象n非static数据成员当前51页,总共58页。类数据成员函数成员static数据成员对象1对象2对象n对象3当前52页,总共58页。§9.5类的静态成员在类成员声明前加上static修饰即把该成员声明为该类的静态成员;static数据成员静态数据成员必须在定义第一个对象之前作为静态变量加以定义并初始化;静态函数成员没有this指针,只能直接访问类中的静态成员(如要访问类中非静态成员,必须借助对象名或指向对象的指针)。静态成员不能说明为虚函数。一般地,每个对象都存放有其所属类中所有成员的拷贝.

非static数据成员但有时希望程序中的同类对象共享某个成员,解决方法:(一)将要共享成员说明为全局变量---破坏封装性(二)将要共享成员说明成类的静态成员---较好当前53页,总共58页。例1:类的静态数据成员classXX{inta;public:

st

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论