继承与类的派生教学.ppt

上传人:小飞机 文档编号:6332974 上传时间:2023-10-17 格式:PPT 页数:118 大小:537KB
返回 下载 相关 举报
继承与类的派生教学.ppt_第1页
第1页 / 共118页
继承与类的派生教学.ppt_第2页
第2页 / 共118页
继承与类的派生教学.ppt_第3页
第3页 / 共118页
继承与类的派生教学.ppt_第4页
第4页 / 共118页
继承与类的派生教学.ppt_第5页
第5页 / 共118页
点击查看更多>>
资源描述

《继承与类的派生教学.ppt》由会员分享,可在线阅读,更多相关《继承与类的派生教学.ppt(118页珍藏版)》请在三一办公上搜索。

1、第五章 继承与类的派生,根据软件需求从软件所要模拟的现实世界中抽象出组成软件系统的对象类是面向对象程序设计的基础。面向对象的封装性使这些对象类的属性和行为细节得到了合理的保护和隐藏,并为类对象之间的通讯(方法调用)提供了安全方便的接口。在封装性的基础上,面向对象的继承性允许一个对象类包含另一个或几个对象类的属性和行为,并使它们成为自己的属性和行为,充分地反映了现实世界中对象类之间的层次结构,为程序的代码重用提供了方便、有效的实现机制。,在面向对象程序设计中,借助继承性的实现方法,允许在既有类的基础上定义新类。被定义的新类可以从一个或多个既有类中继承属性和行为,并允许重新定义这些既有类中原有的属

2、性和行为,还允许为新类增加新的属性和行为,从而形成了类的建造层次。既有类被称为基类或父类 新类被称为派生类、导出类或子类,本章要点1 派生类的概念2 派生类的定义方法3 派生类成员的访问属性4 派生类的构造函数和析构函数5 对派生类成员访问属性的进一步讨论6 多继承7 继承在软件开发的重要意义,5.1 派生类的概念 继承是对象类之间的一种包含关系,这种包含关系是通过对象类的建造层次关系实现的。因此,具有继承关系的类之间必定拥有以下基本性质:类间的共享特性;类间的细微区别;类间的层次结构。,例如:简单的汽车分类图,汽车,5.1.2 使用继承的必要性 试想如果组成一个系统的对象类均为互不包含的独立

3、对象类,则将不可避免出现对象属性和行为的重复冗余,并且这种无层次关系的对象类既不符合现实世界的对象关系,也使对象类的定义、创建、使用和维护复杂化。继承为代码重用和建立类定义的层次结构提供方便有效的手段。例如在一个公司的管理软件设计中需要定义一个客户类 Customer 和雇员类 Employee:,class Customer private:char name15;/姓名int age;/年龄char sex8;/性别double income;/收入 public:void print();/显示输出状态;,class Employment private:char name15;/姓名i

4、nt age;/年龄char sex8;/性别char department20;/部门double salary;/工资 public:void print();/显示输出状态;,比较两个类的定义,不难发现,两个类的数据成员和成员函数有许多相同之处。显然,如此定义两个类,造成的代码重复是不可避免的。如果将 Customer 和 Employee 类定义中的相同成员抽取出来,定义一个新类 Person:class Person private:char name15;/姓名int age;/年龄char sex8;/性别 public:void print();/显示输出状态;,Custome

5、r 和 Employee 都定义为 Person 的派生类,那些在 Person 中已经定义的共同数据成员在 Customer 和 Employee 中就不需要再定义了,只需要在各自的定义中增加自己的独有数据成员;而成员函数 print 也只需要在 Person 所定义的行为操作基础上重新定义自己的行为操作。class Customer:public Personprivate:double income;/收入public:void print();/显示输出状态;,class Employee:public Person private:char department20;/部门doubl

6、e salary;/工资 public:void print();/显示输出状态;显然通过继承可以从基类 Person 派生出一组具有层次结构的新类,构成一个公司管理系统的主要对象类型。例如:,使用继承机制和方法设计和建造类定义的层次结构对于建立一个面向对象的软件系统是不可缺少的。返回,Person,5.2 派生类的定义方法 定义派生类的一般形式:class 派生类名:派生方式 基类名派生类的新增加成员和基类成员的新定义;例如:基类 Person 和派生类的定义class Person private:char name15;/姓名int age;/年龄char sex8;/性别public:

7、void print();/显示输出状态;,class Customer:public Personprivate:double income;/新增加的数据成员“收入”public:void print();/重新定义基类的“显示输出状态”;从形式上比较,派生类定义与非派生类定义的差别仅在于定义首行中由“:”引出的派生表达式。其中:派生方式:指明派生类继承基类成员的方式,方式 的种类有 public、private 和 protected。如果不指明方式名,则缺省指定派生方式为 private。基类名:指明派生类所继承的类。,在 Java 中类的派生又被称为类的扩展,这种扩展相当于 C+中的

8、公有(public)派生,其一般形式如下:class 派生类名:extends 基类名 派生类的新增加成员和基类成员的新定义,5.2.1 派生类的构成 派生类的构成可以有下图示意:,派生类名,例如整数链表类 list 的定义:class list/链表类名超前声明 class node/结点类定义 int val;node*next;public:friend class list;,class list/整数链表类定义 node*elems/链表头指针 public:list();list();bool insert(int);/在表头插入一个结点bool deletes(int);/从表中

9、删除一个结点bool contains(int);/在表中查找一个结点;,一个链表结构的整数集合可以看成是不含重复元素的特殊整数链表,因此整数集合类可以从整数链表类派生。整数集合类在继承了整数链表类的所有成员的基础上,需要新增加一个能指示集合中元素个数的数据成员,同时还需要重新定义整数链表类的插入操作insert,禁止重复元素被插入。class set:public list int card;/集合中的元素个数 public:bool insert(int);/重新定义插入函数;返回,5.3 派生类成员的访问属性 类成员的访问属性是指类成员的被访问权限,这种权限随着作用域的变化而变化的,派生

10、类成员的访问属性也分为类内访问属性和类外访问属性两种情况。1 类内访问属性 由于派生类的成员分为继承的基类成员和自身的新 增成员两种,这两种成员的类内访问属性是有所区 别的。基类成员的访问属性封装性所限定的类成员类外访问权限确定了基类成员在派生类定义中被访问的限定原则:,私有成员:不允许被访问,与派生类从基类 的继承方式无关。公有成员:允许被访问,与派生类从基类的 继承方式无关。新增成员的访问属性所有的新增成员均允许被访问,与新增成员被设定的访问属性(公有或私有)无关。2 类外访问属性 类成员的类外访问是指在类对象定义域外访问对象 的类成员。因此,派生类成员在类定义中声明的访 问属性确定了派生

11、类成员的类外访问属性:,基类成员的访问属性 私有成员:不允许被访问,与派生类从基类 的继承方式无关。公有成员:依据继承方式的不同,在基类中 被设定的公有属性会发生不同的变化。私有继承:基类的公有成员变为派生类的私有成员,因此在类外不允许被访问。公有继承:基类的公有成员在派生类中仍保持公有属性,因此在类外允许被访问。新增加成员的访问属性类成员在类定义中被声明的访问属性确定了类成员的类外访问属性。,类外,例5-1 包含了两个类:由 C+标准模板库提供的 string 类具有丰富的字符串操作功能。edit_string 类是从 string 类派生的,它在继承 string 类功能的基础上增加了数据

12、成员 光标和实现在光标处的进行插入、替换、删除等文本编辑功能。注意,派生类的成员名支配基类的成员名。下面的类图描述了 string 和 edit_string 之间的派生层次结构。,返回,5.4 派生类的构造函数和析构函数5.4.1 派生类的构造函数 与一般非派生类相同,系统会为派生类定义一个缺省(无参数、无显式初始化表、无数据成员初始化代码)构造函数用于完成派生类对象创建时的内存分配操作。但如果在派生类对象创建时需要实现以下两种操作或其中之一,就无法使用缺省构造函数完成。派生类对象的直接基类部分创建需要传递参数。派生类对象的新数据成员需要通过参数传递初值。为了满足上述对象创建操作的需要,就必

13、须显式定义派生类构造函数。,派生类构造函数声明和定义的一般形式:注意:构造函数名后面的参数表列中包含了初始化表中创 建对象的基类部分、新增数据成员和在函数体中为 新数据成员赋初始值所需要的全部参数。,构造函数名(参数表列);,类名:构造函数名(参数表列),:基类构造函数名(参数子表列),新数据成员名1(参数子表列),新数据成员名n(参数子表列),其他初始化代码,初始化表中创建对象的基类部分的表达式必须使用 基类构造函数名调用基类构造函数,而创建数据成 员表达式必须使用数据成员名调用数据成员类的构 造函数。派生类构造函数的执行顺序:,基类构造函数,对象成员1类构造函数,派生类构造函数定义体,对象

14、成员n类构造函数,5.4.2 派生类的析构函数 与一般非派生类相同,系统会为派生类定义一个缺省(无数据成员的清理代码)析构函数用于完成派生类对象撤消时的内存回收操作。但如果在派生类对象撤消时需要对某些新增数据成员进行内存回收之前的清理操作(例如,指针数据成员所指向的动态内存的回收),就无法使用缺省析构函数完成。为了满足上述对象数据成员清理操作的需要,就必须显式定义派生类构造函数。,析构函数的执行顺序:,派生类析构函数定义体,对象成员n类析构函数,基类析构函数,对象成员1类析构函数,5.4.3 派生类应用的实例例5-2 中定义了一个人员类 person,并以 person 为基类 派生定义了学生

15、类 student 和教师类 teacher。另外在学生类 student 中还包含了一个 teacher 类对象作为描述学生班主任的数据成员。三个类都分别定义两个构造函数(其中一个有参数表列,另一个无参数表列)和一个析构函数。通过不同形式的 student 类对象和 teacher 类对象定义表达式所导致的相应类的不同构造函数的调用,验证派生类对象创建和撤消中,基类构造函数、数据成员类构造函数和派生类构造函数的调用顺序。,几点讨论:1 如果派生类构造函数定义中无显式初始化表,则意 味着派生类对象的基类部分创建时,调用基类构造 函数无须参数;新增数据成员创建时,调用相应数 据类构造函数也无须参

16、数。因此,如果基类和相应 的数据类没有定义无参数或有缺省参数值的构造函 数,将会导致编译错误。由此可见,一般情况在类 的定义中保留一个无须传递参数的构造函数是十分 必要的,除非需要禁止无参数创建类的对象。,无显式初始化表的派生类构造函数的一般形式:系统的缺省构造函数是这种形式的一个特例,即无参数,无显式初始化表和空定义体的类构造函数。,类名:构造函数名(参数表列)新增数据成员赋初始值代码,类名:构造函数名(),2 一般情况下,类数据成员的赋初始值操作均可以在 数据成员创建(分配内存)的同时进行,因此可以 通过初始化表同时完成数据成员的创建和赋初始值 操作。在这种情况下,如果对数据成员不需要其他

17、 创建之后的初始化操作,就可能出现具有空定义体 的构造函数。具有空定义体的构造函数的一般形式:,类名:构造函数名(参数表列),:基类构造函数名(参数子表列),新数据成员名1(参数子表列),新数据成员名n(参数子表列),3 在多层次派生类构造函数的初始化表中的基类部分 表达式一般只涉及直接基类和新增数据成员的创建 和初始化操作,而间接基类的创建和初始化操作则 由直接基类的构造函数定义完成。这种分层次的构 造定义有利于简化程序编码和提高源代码的可读 性。当然,在某些特殊情况下,为了满足某种特定 要求,也允许在派生类构造函数的初始化表中对间 接基类部分进行必要的创建和初始化操作(在多重 继承将介绍这

18、种情况的实例),但不提倡滥用。,例5-3 用高斯消元法来求线性方程组。1 问题分析所谓高斯消元法就是通过线性方程组的系数矩阵对方程组进行一系列等价变换,使得变换后的系数矩阵为一个对角线元素均为 1 的三角矩阵,然后通过逐步回代,求得方程组的解。例如,下面的三元一次方程组:2x+4y+5z=55-2x+5y 2z=205x+5y z=81使用高斯消元法对该线性方程组的系数矩阵进行等价变换的过程和逐步回代求解的过程如下所示:,5 5-1 81 2 4 5 55-2 5-2 20,1 1-0.2 16.2 2 4 5 55-2 5-2 20,1 1-0.2 16.2-2 5-2 20 2 4 5 5

19、5,1 1-0.2 16.20 7-2.4 52.42 4 5 55,1 1-0.2 16.20 7-2.4 52.40 2 5.4 22.6,1 1-0.2 16.20 1-0.34 7.50 2 5.4 22.6,1 1-0.2 16.20 1-0.34 7.50 0 6.1 7.6,1 1-0.2 16.20 1-0.34 7.50 0 1 1.2,z=1.2 y=7.5+1.2*0.34=7.9x=16.27.9+0.2*1.2=8.5,回代求出方程组的解:,为实现上述操作功能,需要定义了矩阵类 matrix 作为对线性方程的系数矩阵进行操作的基类,它所提供的操作功能:构造函数:根据指

20、定的行和列构造相应的矩阵;重载调用运算符 operator():根据索引的行、列值,引用相应的矩阵元素;输出显示函数:格式显示矩阵的全部元素值。,线性方程组类 lineqns 从 matrix 派生,主要操作有:构造函数:用传递的方程个数和解进行初始化;参数产生:产生方程组的各变量系数值和常量值,从而构造方程组;高斯求解函数:使用消元法求解方程组。lineqns 和 matrix 派生关系:,matrix,lineqns,2 详细设计 类设计 matrix 类类定义:class matrix short rows,cols;double*elems;public:matrix(short ro

21、ws,short cols);matrix();double,lineqns 类类定义:class lineqns public matrixint neqns;double*solution;public:lineqns(int n,double*soln);lineqns()void generate(int coef);void solve();算法描述:,generate:用于产生方程的变元系数和常数generate(coef)参数 coef 指定系数的值域范围BEGIN 计算系数的中值 mid=coef/2;for i=1 to 方程个数 n,step=1 设置方程组矩阵中的常数(i

22、,n+1)的初值为0;for j=1 to 变量个数 n,step=1计算系数(i,j)=mid rand()%coef;计算常数(i,n+1)+=系数(i,j);endfor endforEND,solve:高斯消元求解使用 N-S 流程图描述,图中的符号约定说明:diag系数矩阵主对角线元素的行、列标识;piv同列系数中最大元素值的行标识;neqns方程组中的方程个数标识;r行序号循环标识;c列序号循环标识;factor用于消去指定系数元的变换因子标识;print显示系数矩阵的功能函数标识;soln线性方程组的解矩阵标识;sum求解过程中累加和标识。a系数标识。C常数标识。,piv=dia

23、g,r=r+1,piv=r,终止求解,并退出,第diag 行中的系数和常数逐个除以系数a(diag,diag),使系数a(diag,diag)=1,factor=-系数a(r,diag),使系数a(r,diag)-a(r,diag)=0,r=r+1,调用(基类的)print 成员函数输出消元后的方程组系数矩阵,类应用 main 函数的算法:返回,创建存放方程解的数组:soln=new doubleneqns,从系数矩阵直接获取最后一个变元的解:solnneqns-1=a(neqns,neqns+1),输入线性方程组的方程个数和相应的解,根据指定的方程个数和解,创建 lineqns 类对象 eq

24、n,调用 eqn.generate 为方程组设置系数和常数,调用 eqn.print 输出显示所建方程组的系数矩阵,调用 eqn.solve 求解所建线性方程组,sum=0,solnr-1=常数C(r,neqns+1)-sum,5.5 对派生类成员访问属性的进一步讨论 前面我们已经对派生类成员的基本访问属性进行了讨论,从讨论中我们发现,要使派生类与继承的基类成员更加“无缝”结合、更加灵活可控地继承、有两个问题还需要进一步讨论并加以解决。这两个问题是:基类私有成员在派生类中不可直接访问性与派生类 新增成员函数需要能直接访问基类私有成员提高行 为操作效率和灵活性之间的矛盾。继承方式对基类成员的设定

25、访问属性修改的局限性 与派生类期望能更加灵活、可控制地从基类继承之 间的矛盾。,5.5.1 保护成员与保护继承1 类成员的保护访问属性 解决基类私有成员在派生类中只能通过基类的接口(公有成员函数)访问而不允许直接访问的思路 是:在不破坏派生类封装性的前提下,“突破”基类 的封装边界。解决的方法之一是增加一种新的类成 员访问属性 保护访问属性:一般形式:protected 类型名 数据成员名;protected 类型名 成员函数名(参数表列);访问权限:可以在类内和派生类内被访问,而在 类外和派生类外不允许被访问。,访问权限的继承:私有派生:基类的保护成员在派生类中将变 成私有成员。公有派生:基

26、类的保护成员在派生类中保持 保护访问属性。具有保护访问属性的类成员称为保护成员。将派生 类需要直接访问的基类私有成员定义为基类保护成 员,既可以提高这些基类成员在派生类内的访问效 率和方便性,又保持了这些类成员在派生类外不能 被直接访问的数据隐藏性。,例5-4定义了能确定显示位置的基类 location,该类包含两个整型的保护数据成员 x 和 y。由 location 公有派生一个点类 point,该派生类具有能在确定的位置处显示、隐去和移动到指定位置的操作功能。再由 point 类私有派生一个圆类 circles,该派生类继承了间接基类 location 和直接基类 point 的全部成员,

27、并重新定义各项操作。,2 类派生的保护继承方式 类派生的继承方式的作用是确定了基类成员被继承 到派生类中成为派生类成员时,其访问属性被限定 修改的规则。增加保护继承方式的目的是使派生类 成员的类外访问属性与私有继承方式相同,而当派 生类被再次派生时,直接访问间接基类成员提供可 能性。一般形式:class 派生类名:protected 基类名 类成员定义代码;,基类成员访问属性修改规则:私有成员:与公有继承方式和私有继承方式 相同,在派生类内外均不允许被访问。保护成员:基类的保护成员在派生类中保持 保护访问属性。公有成员:基类的公有成员在派生类中变为 保护成员。下面用图表来归纳和描述基类的 pr

28、ivate,protected 和 public 三种类成员在以 private,protected 和 public 三种继承方式派生的新类中的访问属性的变化。,私有派生方式继承,protected:public:,interCode,NameAddressAreaCodephone,Person()Person()Person inputPerson()void prPerson(),protected:public:,NameAddressAreaCodePhonePerson inputPerson()void prPerson()department,yrsWork,Employee

29、()Employee()int testYears(),class Person,class Employee:private Person,保护派生方式继承,protected:public:,interCode,NameAddressAreaCodephone,Person()Person()Person inputPerson()void prPerson(),protected:public:,NameAddressAreaCodePhonePerson inputPerson()void prPerson()custBalance,Costomer()Costomer()void P

30、rtCust(),class Person,class Costomer:protected Person,custNum,公有派生方式继承,protected:public:,interCode,NameAddressAreaCodephone,Person()Person()Person inputPerson()void prPerson(),protected:public:,NameAddressAreaCodePhonevendOwed,Vendor()Vendor()Person inputPerson()void prPerson()void PrtVend(),class P

31、erson,class Vendor:public Person,vendNum,基类成员在派生类内外的访问属性一览表,5.5.2 派生友元类 如果希望基类的私有成员只在派生类中能被直接访问,而不希望这种直接被访问的属性从派生类向下一层次的派生类中延续,则在基类定义中将要派生的类声明为基类的友元,即从基类派生友元类。当然,也可以将基类的私有成员定义为保护成员,然后使用私有继承方式定义派生类的方法得到相同效果。例如:class set;struct node int val;node*next;,class list node*elems;public:friend class set;clas

32、s set:public list int card;public:set operator+(set,5.5.3 访问域声明 所谓访问域声明是在私有继承方式定义的派生类中对基类的公有成员和保护成员进行声明,调整它们在派生类中访问属性,使这些基类成员保持它们在基类定义中设定的访问属性。显然,在保护继承方式定义的派生类中,访问域声明只对基类的公有成员有效,因为基类的保护成员在派生类中已经保持了基类定义中原有访问属性。而在公有继承方式定义的派生类中,访问域声明是没有意义的,因为基类的公有成员和保护成员在派生类中都保持了基类定义中原有访问属性。,使用访问域声明可以有效地控制在派生类外,基类的某些公有

33、成员可以被访问,而某些公有成员被隐藏。还可以使派生类能够地向下一层次的派生类有选择地提供其基类的保护成员和公有成员。对基类成员进行访问域声明必须遵守以下规则:1 访问域声明仅能调整对基类成员名,而不能为基类 成员重新说明类型,即便所说明的类型与基类成员 的原有类型相同,也是不允许的。如果声明的是成 员函数,则声明的也只是函数名而不准带有参数。例如:,class x int a;public:int b;int f(int i,int j);class y:x public:int x:b;/错误x:f(int i,int j);/错误;,正确的访问域声明如下:class y:x public:

34、x:b;/正确x:f;/正确;2 访问域声明只能使基类的保护和公有成员在派生类 中保持它们在基类定义的设定的访问属性,而不能 改变基类的私有成员在派生类中的访问属性,任何 试图这样做的行为都被视为破坏封装性,是非法 的。例如:,class xint a;public:;class y:xx:a;/非法public:;,3 访问域声明仅用于在派生类中保持基类(公有或保护)成员的原有访问属性,不允许修改它们的访问属性。也就是说,基类的保护成员只能在派生类的保护段中进行声明;而基类的公有成员只能在派生类的公有段中进行声明。例如:class x int a;protected:int b;public

35、:int c;,class y:x public:x:b;/错误 protected:x:c;/错误;正确的访问域声明应为:class y:x public:x:c;protected:x:b;,4 在派生类中对基类的重载成员函数名的访问域声明 将调整基类中所有以该名命名的成员函数的访问属 性。例如:class x public:f();f(int);f(char*);,class y:x public:x:f;在派生类中说明了 x:f 后,基类 x 中所有以 f 命名的 成员函数在派生类中都保持原有的公有访问属性。若基类中的这些重载成员函数处在不同的访问域,那么,在派生类中就不能进行访问域声

36、明。例如:,class x f(float);protected:f(double);public:f();f(int);f(char*);,class y:x public:x:f;/错误;导致错误的原因:f(float)是基类私有成员,试图改变该成员函数的私有访问属性是绝对不允许的;f(double)是基类保护成员,试图改变该成员函数的保护访问属性也是不允许的。,5 如果在派生类中具有与基类中同名的类成员,则基 类中的此成员不允许在派生类中进行访问域声明,否则将产生二义性错误。例如:class x public:f();f(int);f(char*);,class y:x public:v

37、oid f(float);x:f;/二义性错误;返回,5.6 多继承5.6.1 多继承的概念 所谓多继承就是一个新类是从多个基类中派生而成的。例如,在一个面向对象的图形用户界面中,为用户界面提供的窗口、滚动条、对话框和各种操作按钮都是通过类对象来实现的。如果希望在既有类的基础上定义一个新类具有两个以上既有类的全部属性和操作,就可以通过多继承的方法完成。如,可以由窗口类和滚动条类共同派生出一个可滚动的窗口新类。,在有些情况下,可以使用类的组合关系,即将一些 既有类对象定义为新类的成员来实现与多继承派生 的新类相同的功能。例如,同样是定义可滚动的窗 口类,可以以窗口类为基类单继承派生,并将滚动 条

38、类对象作为新类的新增成员。在新类中窗口类的 属性和行为被继承,而滚动条的属性和行为并没有 被继承,对滚动条对象成员的使用是通过新类的内 部消息实现的。,5.6.2 多继承的定义 多重继承派生类定义的一般形式:class 派生类名:继承方式 基类名1,继承方式 基类名n 派生类新增的数据成员和成员函数;注意,在每个基类名之前必须有继承方式,如果缺省继承方式,则表示从该基类私有派生。例如:class c:public a,b;/c 对 a 公有派生,对 b 私有派生class c:a,public b;/c 对 a 私有派生,对 b 公有派生class c:public a,public b;/c

39、 对 a,b 均为公有派生,如果多继承派生的多个基类中有同名的类成员,则派生类定义中访问该同名成员时必须使用不同基类名和名域运算符加以区别,否则将导致二义性。例如:class x protected:int a;/同名数据成员 public:void make(int i)a=i;/同名成员函数;,class y protected:int a;/同名数据成员 public:void make(int i)a=i;/同名成员函数;class z:public x,public y public:int make()return x:a*y:a;/避免对基类中的同名数据成员 a 的二义性访问;,

40、在派生类外访问基类的同名成员时也须使用不同基类名和名域运算符加以区别,否则也将导致二义性。例如:main()z z1;z1.x:make(10);z1.y:make(20);cout z1.make()n;return 1;,如果使用组合关系定义派生类z:class z:public x public:y y1;int make()return a*y1.a;则可以避免上述的二义性错误。,5.6.3 多继承的构造函数和析构函数1 构造函数的定义 与单继承派生类相同,在如下情况时必须显式定义 多继承派生类构造函数:派生类对象中只要有一个直接基类部分的创建需要传递参数。派生类对象的新数据成员需要通

41、过参数传初值。多继承类构造函数的定义的一般形式:,例如:class window/定义窗口类 public:window(int top,int left,int bottom,int right);window();,构造函数名(参数表列);,派生类名:构造函数名(参数表列):,基类1构造函数(参数子表列),基类n构造函数(参数子表列),新数据成员1(参数子表列),新数据成员n(参数子表列),其他初始化代码;,class scrollbar/定义滚动条类 public:scrollbar(int top,int left,int bottom,int right);scrollbar();c

42、lass scrollbarwin:window,scrollbar/定义派生类 public:scrollbarwin(int top,int left,int bottom,int right);scrollbarwin();,scrollbarwin:scrollbarwin(int tp,int lt,int bm,int rt):window(tp,lt,bm,rt),scrollbar(tp,rt-20,bm,rt),2 派生类构造函数和析构函数的执行顺序 与单继承派生类对象创建时构造函数的调用顺序相 同,先构造基类部分,再构造派生类。多个基类构 造函数的执行顺序与定义时从左到右的

43、基类排列顺 序一致。派生类新增数据成员对象的构造函数的执 行顺序也与定义时的先后排列顺序一致。,基类 1 构造函数,基类 n 构造函数,成员对象 1 构造函数,成员对象 n 构造函数,派生类构造函数定义体,派生类对象撤消时,析构函数的调用顺序与对象创 建时构造函数的调用顺序相反。,基类 1 析构函数,基类 n 析构函数,成员对象1 析构函数,成员对象 n 析构函数,派生类析构函数定义体,3 多继承派生类编程实例例5-5 在屏幕上显示一个带有字符串的圆。1 问题分析 定义一个圆类 circles 和字符串类 gmessage 分别用于 在屏幕的指定位置按设定半径绘制圆和在屏幕的指 定位置显示特定

44、的字符串。为了使所显示的圆和字 符串的位置紧密相关,采用多继承机制,从 circles 和 gmessage 派生出新类 mcircle 用于完成本例的最 终需求。另外为了更好地体现面向对象的设计思 想,再定义一个 point 类,用于确定所要显示的圆和 字符串的位置。因此 circles 和 gmessage 都应该从 point 派生。,类图描述,例5-6 一个远程网络中记录两台机器之间的平均传输时间如下表所示:编写一个能按照网络通讯的出发地和目的地输入、保存传输时间,并能按照上述表格形式输出已经保存的网络通讯时间。,1 问题分析:使用矩阵结构保存二维表格数据是最为恰当的。但 由于本需求中

45、二维表中的行、列位置的不是整数下 标而是字符串,所以描述该表格不能直接使用通用 矩阵类,而需要使用以字符串指示位置的特殊矩阵 类。其中,作为行、列下标的字符串可以通过向量 类将字符串映射为指示通用矩阵元素的整数下标。显然该特殊矩阵是由向量和通用矩阵协同工作实现 的。为此,定义通用矩阵类 Matrix 用于完成通讯网 络表的基础操作,再定义向量类 AssocVec 用于将 通讯地址描述串转换为矩阵元素下标,而网络通讯 传输表类 Table 可以从这两个类派生。这些类之间 的关系如下图所示:,2 类的设计实现 向量结构 VecElem:struct VecElem char*index;/索引字符

46、串int value;/映射变量;,辅助向量类 AssocVec:class AssocVec public:AssocVec(int dim);/构造函数 AssoVec();/析构函数 int,下标运算符 AssocVec:operator(char*idx)算法:,索引字串复制到elemsused+1.index使elemsused+1.value=used+1,并返回elemsused+1.value的引用,返回值为-1的静态哑变量引用,指示调用失败,Yes,No,Yes,No,根据参数指定的索引字串查询已有向量,返回匹配映射变量的引用,通用矩阵类 Matrix:class Matri

47、x public:Matrix(int rows,int cols);/构造函数 Matrix();/析构函数 double/二维矩阵的行、列索引;,调用运算符 Matrix:operator()(short r,short c)的算法:,返回参数指定的要访问的矩阵元素:elems(r-1)*(c-1)的引用,返回静态哑变量的引用,Yes,No,网络通讯传输表类 Table:class Table:AssocVec,Matrix public:Table(short entries);/构造函数 double;,调用运算符 Table:operator()(char*src,char*dest

48、)的算法:,调用 AssocVec:operator 将参数索引字串 src 和 dest 映射为整数索引 r 和 c,调用 Matrix:operator(r,c)返回 r 和 c 确定的矩阵元素的引用,5.6.4 虚基类1 为什麽要使用虚基类 在多继承派生中一种可能产生二义性的情况:派生类有多个直接基类是同一个间接基类的派 生类;在派生类中需要访问共同间接基类的成员。例如:class base/共同的间接基类 protected:int a;public:base()a=5;,class base1:public base/直接基类1 public:base1()cout base1 a=

49、a endl;class base2:public base/直接基类2 public:base2()cout base2 a=a endl;,class derived:public base1,public base2 public:derived()cout derived a=a endl;/二义性:base1:a 还是 base2:a?;main()derived obj;return 0;,base,base,base1,base2,derived,2 虚基类的概念 显然,解决上述二义性问题的办法是使派生类对象 层次结构中只有一个间接基类 base 实体。C+允许 在派生类的定义中

50、使用关键字 virtual 将基类说明为 虚基类来实现此目的。用虚基类重新定义上例中的 直接基类:class base1:virtual public base public:base1()cout base1 a=a endl;,class base2:virtual public base public:base2()cout base2 a=a endl;class derived:public base1,public base2 public:derived()cout derived a=a endl;/访问 base:a 具有唯一性;,又例如,在定义图形用户界面时,可以考虑定义这

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

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号