第七章 继承性.ppt_第1页
第七章 继承性.ppt_第2页
第七章 继承性.ppt_第3页
第七章 继承性.ppt_第4页
第七章 继承性.ppt_第5页
已阅读5页,还剩56页未读 继续免费阅读

下载本文档

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

文档简介

1、面向对象程序设计 第二版,7.1 继承的好处 7.2 C +定义派生类的语法 7.3 基类成员的访问属性 7.4 派生类对象的初始化 7.5 多重继承 7.6 基类与派生类之间的转换 7.7 小结,第七章 继承性,7.1 继承的好处,当原来使用的数据结构不适应新系统的需求,或原来提供的功能需要扩充,或原来的性能不能满足现在的要求时,派生类的成员函数可以调用基类的成员函数,并在此基础上增加必要的程序代码; 当需要完全改变原有操作的实现算法时,可以在派生类中实现一个与基类成员函数同名而算法不同的成员函数; 当需要增加新功能时,可以在派生类中定义一个新的成员函数。 继承性还使得用户在开发新的应用系统

2、时不必完全从零开始,可以继承原有的相似系统的功能或者从类库中选取适用的类,再派生出新的类以实现所需要的功能。,面向对象程序设计 第二版,下面让我们通过一个具体例子来感受一下使用继承机制的必要: class String private: char *contents;/说明了一个字符串首地址的指针 int length;/字符串长度 public: String() length = 0; int get_length() return length; char *get_contents() return contents; void set_contents(int in_length,c

3、har *in_contents); void set_contents(char *in_contents); ;,面向对象程序设计 第二版,可以利用C +语言提供的继承机制,从String类派生出新的派生类EditString,增加了移动光标,在光标处编辑的处理功能: class EditString : public String/派生类EditString的定义 private: int cursor; public: EditString(int offset = 0) cursor = offset; int get_cursor() return cursor; void mov

4、e_cursor(int offset);/ 把光标移到指定位置 void add(String *text);/ 在光标后添加一个字符串 void replace(String *text);/ 把光标后内容换成新字符串 void del(int how_much); / 删除光标后指定个数的字符 ;,面向对象程序设计 第二版,下面是使用String类和EditString类的一个主函数: void main() String s1;/声明基类String的对象s1 EditString s2;/声明派生类EditString的对象s2 char *p; s1.set_contents(11

5、,Hello, world); p = s1.get_contents(); s2.set_contents(11, p); s2.move_cursor(6); s2.replace(“Students”); ,面向对象程序设计 第二版,为了软件复用,必须具有: 抽象机制 将一组相似模块中的共性内容抽象出来,作为基类的内容向外部世界共享。 继承机制 无需修改原有程序模块的代码就可以重用原有的程序资源并对其功能进行扩充。,7.2 C+定义派生类的语法 7.2.1 定义派生类的语法,面向对象程序设计 第二版,图7.1书写父类表的语法,7.2.2 访问权修饰符,面向对象程序设计 第二版,表7.1父

6、类成员在子类中的访问属性,三条规律: 派生类成员函数不能直接访问基类的私有成员。 派生类继承了基类的私有成员,在创建派生类对象时,先为基类中说明的数据成员(包括私有数据成员)分配内存,然后才为派生类中说明的数据成员分配内存,所有上述成员都属于这个新创建的对象。 如果在子类中需要使用从父类继承来的父类私有成员,必须通过父类公有(或保护)的成员函数访问。,面向对象程序设计 第二版, 访问权修饰符只能使继承来的成员的可访问性受到更严格限制,而不能放松这种限制。 在继承机制下信息隐藏的最好方法,是在基类中使用访问权符protected。 在基类中使用访问权符protected声明的成员,可由派生类的成

7、员函数直接访问,在派生类之外,任何函数是不能直接访问的。通常把被派生类继承后需较频繁使用的基类数据成员的访问属性规定为protected。,使用访问权修饰符时的注意事项: 要区分访问权修饰符与访问权符的概念,在定义派生类时,派生类通过访问权修饰符从基类获得一定权限,进行公有派生、保护派生或私有派生,例如:class B:public A;,这时语句中的public即为访问权修饰符;而当一个类定义过程中,有些数据成员被封装在公有部分,有些数据成员被封装在私有部分,而这时使用的public、protected及private即为访问权符。 如果访问权修饰符为public,则派生类中方法和数据成员的

8、访问权限不变(同基类),但如果访问权修饰符为protected或private,则派生类中的方法和数据成员的访问权限变小,具体请参见表7.1父类成员在子类中的访问属性加以理解。,面向对象程序设计 第二版,在实际开发过程中,允许其他类使用的方法(即成员 函数)及数据成员(变量)应该定义在public访问权符之下 的部分,为外界提供一个服务接口。 准备作为继承或派生的方法或数据成员应该定义在 protected访问权符之下的部分。将仅作为本类使用的方法 或数据成员应该定义在private访问权符之下的部分。 不建议把类属性说明在public访问权符之下的部分,通 常说明于protected与pri

9、vate访问权符之下的部分,以保证 程序的安全可靠。,面向对象程序设计 第二版,7.3 基类成员的访问属性,基类成员被派生类继承之后,它的访问属性与 原来在基类中的访问属性可以有所不同。调整基类 成员访问属性的基本手段是访问权修饰符,在上一 节中已经系统全面地讲述了访问权修饰符对基类成 员访问属性的调整作用,本节进一步讲述调整基类 成员访问属性的其他方法,以及基类特殊成员在派 生类中的访问属性。,面向对象程序设计 第二版,7.3.1 同名成员,为了在派生类中使用基类的同名成员,必须在 该成员名之前加上基类名和作用域分辨符“:”作为 限定符,即必须使用下列格式才能访问到基类的 同名成员: 基类名

10、 : 成员名,面向对象程序设计 第二版,C+语言允许在派生类中说明的成员与基类中的成 员名字相同,称派生类成员覆盖了基类的同名成员,在派 生类中使用这个名字意味着访问在派生类中重新定义的成 员。为了在派生类中使用基类的同名成员,必须在该成员 名之前加上基类名和作用域分辨符“”作为限定符。 例如: class Base protected: int a,b; public: Base(int i,int j) a=i; b=j;/基类中说明的数据成员b ;,面向对象程序设计 第二版,class Derived:public Base int b,c; public: Derived(int x1

11、,int x2,int x3,int x4):Base(x1,x2) b=x3;/派生类中说明的数据成员b c=x4; void set_c(int x) c=x+b+Base:b; /b为派生类中的b(屏蔽了基类中的 /b),Base:b为基类中的b(在派 /生类中使用基类中的成员) ;,面向对象程序设计 第二版,下面我们再看一个同名成员函数屏蔽的例子: #include “stdafx.h” #include “iostream.h” class Base public: void fun( )cout Base : fun() endl; void fun(int i)cout Base

12、 : fun(int i) endl; /说明了一个fun()函数的重载函数 ; class Derive :public Base public: void fun( )cout Derive : fun( ) endl; /在派生类中说明一个fun( )函数 ;,面向对象程序设计 第二版,void main( ) Base e;/说明了一个基类对象 e.fun( );/通过基类对象调用的是基类中的成员函数fun( ) e.fun(3);/通过基类对象调用的是基类中的重载的成员函数fun(int i) Derive d;/说明了一个派生类对象 d.fun( );/通过派生类对象调用的是派生类

13、中的成员函数fun( ), /基类的fun( )及其他重载的成员函数均被屏蔽 /d.fun(3);/此行若不是注释,编译将会出现错误 d.Base : fun( );/利用派生类对象,通过“类名 : ”限定,调用基类 /的成员函数 d.Base : fun(3);/调用基类的重载成员函数 ,面向对象程序设计 第二版,运行结果: Base : fun() Base : fun(int i) Derive : fun() Base : fun() Base : fun(int i),注意: 派生类屏蔽基类中的同名成员函数,是指屏 蔽了基类中的该同名函数及其所有重载函数。 由于派生类中“fun()”

14、函数屏蔽了基类中“fun()” 函数的所有版本,因此,“d.fun(3);”的操作,会 出现错误提示“error C2660: fun : function does not take 1 parameters”。,面向对象程序设计 第二版,7.3.2 静态成员,不论是公有派生还是私有派生,派生类都可以 使用基类的静态成员,使用时唯一的要求是,必须 用“类名:成员名”代表这个静态成员。例如: class Base public: static void a(); void b(); ; class Derived:private Base . ; class DD:public Derived

15、 void func(); ;,面向对象程序设计 第二版,void DDfunc() Basea(); / 编译通过 a(); / 编译出错 b(); / 编译出错 ,在上面的程序片断中,Derived类从Base类私有派生,继承来的函数b已经成为Derived类的私有成员函数,因此,它的子类DD不能直接使用函数b; 函数a是Base类的静态成员函数,它不受访问权修饰符的影响,在间接派生类DD中仍然可以直接使用,但是,使用它的时候必须用“类名:函数名”显式指定它,否则会产生编译错误。,面向对象程序设计 第二版,在静态成员函数的实现中不能直接使用类中声明的非静态成员,可以使用类中声明的静态成员。

16、如果需要使用非静态成员时,可通过对象使用。 例子:,面向对象程序设计 第二版,例7_1,7.3.3 访问声明,当一个类B是从其父类A私有派生的时候,它所继承的父 类公有段或保护段的所有成员,在这个派生类中的访问属性 都成为私有的,如果从类B再派生出类C,则类C不能直接访 问其间接基类A的任何非静态成员。为了使程序员具有必要的灵活性 ,以方便应用系统的开发,C+语言提供了一种调节机制,称为访问声明,它是对私有派生方法的一种补充。 举例说明使用访问声明的方法: (续下页),面向对象程序设计 第二版,class Base int x; public: int y,z; int f(int i); ;

17、 class Derived:private Base/Derived类从Base类私有派生 int a; public: Basey; / 调整Basey的访问属性 int b; void p(); ;,面向对象程序设计 第二版,上例中Base类公有段的所有成员原本都应该成 为Derived类的私有成员,但是,使用访问声明: Base:y 把Base类的公有成员y在私有派生类Derived中 显式声明为公有的,就使得Derived类的派生类仍 然可以直接访问它。,面向对象程序设计 第二版,使用访问声明机制时应该注意以下几点规则: 访问声明仅仅调整名字的访问属性,不允许为它说 明任何类型。 不

18、允许利用访问声明机制,在派生类中提高或降低 基类成员的可访问性,也就是说,基类的公有段或保护段的 成员,在派生类中的访问属性仅能利用访问声明机制相应 地说明为公有的或保护的。 (参看书p158第3行开始的例子),面向对象程序设计 第二版, 对重载函数名的访问声明,将调整基类中具有该名字的所有成员函数的访问属性 ,在基类中访问属性不同的重载函数名不能通过访问声明调整其访问属性,否则与规则(2)发生矛盾。 (参看书p158例子) 如果派生类中说明了一个与基类成员同名的成员,则不能使用访问声明调整该名字的访问属性,否则在派生类中会形成对同一名字的二次说明。,面向对象程序设计 第二版,定义一个派生类之

19、后,它将继承其基类中的全部成员。创建了派生类的一个对象之后,该对象中不仅包含在派生类中声明的数据成员,而且还包含它从基类继承的全部数据成员。 也就是说,在创建派生类的对象时,编译程序不仅要为这个派生类中声明的数据成员分配内存空间,还要为它的基类中声明的数据成员分配内存空间,怎样在这些内存空间中放置初始数值呢?这就是派生类对象的初始化问题。 派生类对象的初始化与非派生类对象初始化的主要区别在于,既要完成派生类本身声明的数据成员的初始化工作,又要同时完成从其基类继承来的数据成员的初始化工作。,7.4 派生类对象的初始化,派生类对象的初始化问题实质上就是,当创建派生类的一个对象时,怎样在调用派生类本

20、身构造函数的同时也调用基类的构造函数。C+通过在派生类构造函数中列出基类初始串列的方法,提供了这种初始化基类数据成员的机制。 C+语言定义派生类构造函数的格式为: 派生类名(变元表):父类名1(变表),父类名m (变元表),对象成员名1(变元表),对象成员名n(变元表) . 下面的程序片断举例说明了派生类对象的初始化方法:,面向对象程序设计 第二版,class Base int p1,p2; public: Base(int i1,int i2) p1=i1; p2=i2; ,class Derived:Base int p3; Base obj; / 对象成员 public: /* 注意下面

21、的构造函数定义:派生类构造函数参数表中的各参数均为形参,而基类构造函数和对象初始串列中的各参数均为实参。 */ Derived(int x1, int x2, int x3, int x4, int x5) : Base(x1, x2) , obj(x3, x4) p3=x5; ; main() Derived d(27,28,100,200,-50); ,在上列的主函数main中,说明Derived类的对 象d时,自动调用构造函数,先父类(即将p1、p2 赋值为i1、i2即27、28),再类对象(即把obj对 象成员中的obj.p1、obj.p2赋值为obj.i1、obj.i2即 100、2

22、00),最后自己(即将p3赋值为-50),经 过上述过程,结果把各个数据初始化为: p1 = 27,p2 = 28, obj.p1 = 100, obj.p2 = 200, p3 = -50,面向对象程序设计 第二版,关于派生类对象的初始化,最后再说明几点: 在派生类构造函数的初始串列中,使用父类类名 来调用父类构造函数,使用对象成员名来调用内层 类的构造函数。 当使用父类或内层类的有参数的构造函数来完成 基类成员或对象成员的初始化时,即使派生类构造 函数本身无需完成任何工作(函数体为空),也必须 定义派生类的构造函数。,面向对象程序设计 第二版,如果在定义派生类构造函数时省略父类初始串列,

23、则意味着使用父类的缺省构造函数来初始化基类成 员。在这种情况下如果父类中只定义了有参数的构造 函数,而没有定义无参数或全部参数都有缺省值的构 造函数,则在编译时会产生编译错误。 派生类构造函数的执行顺序是,先父母(执行父类构 造函数),再客人(初始化对象成员),最后自己(初始化 派生类本身的普通数据成员)。,面向对象程序设计 第二版,7.5 多重继承 7.5.1 语法,派生类还可以有多个直接基类,这种继承关系 称为多重继承,也称为多继承。定义多重继承的 派生类与定义单继承的派生类的唯一差别是,在 父类表中应该列出多个父类名,每个父类名之前 可以冠以关键字private或public;也可以省略

24、这些 访问权修饰符而使用缺省的访问权修饰符,不同 父类名之间应该用逗号“,”隔开。,面向对象程序设计 第二版,7.5.2 同名引出的二义性,当多重继承的派生类的不同父类中含有同名 成员时,这些成员都会被派生类所继承,如果在 派生类中简单地通过名字来使用这些成员,就会 产生二义性。为了避免二义性,在派生类中使用 不同父类的同名成员时,必须在成员名之前用基 类名加作用域分辨符来限定,以明确指出所使用 的成员是从哪个基类继承来的。,面向对象程序设计 第二版,面向对象程序设计 第二版,make为方法名 i 为形参 in表示i为输入参数 int表示i的类型,下面的简单程序说明了同名带来的二义性及避免二义

25、性的方法。例如: 为了实际工程中不鼓励使用同名,在任何有可能引起误解的地方,要避免使用相同的名字。,面向对象程序设计 第二版,例7_2,7.5.3 公共基类带来的二义性,面向对象程序设计 第二版,图7.3 公共基类带来的二义性,#include ”iostream.h” class Base protected: int a; public: Base(int i) a=i; ; class Derived1:public Base int d1; public: Derived1(int p1,int p2):Base(p1) d1=p2; ; class Derived2:public B

26、ase int d2; public: Derived2(int x1,int x2):Base(x1) d2=x2; ;,面向对象程序设计 第二版,class DD:Derived1,Derived2 public: DD(int i1,int i2,int i3,inti4):Derived1(i1,i2),Derived2(i3,i4) void display() cout a n; ; 当编译程序分析到DD类的成员函数display时,将给出出错信息,指出存在二义性,什么原因呢?,面向对象程序设计 第二版,为了消除因公共基类而引出的二义性问题,同样必须使用基类名加作用域分辨符来限定有

27、二义性的成员名。如果想输出从Derived1类继承来的数据成员a,则应该把输出语句写为: cout Derived1:a n; 反之,如果要输出从Derived2类继承来的数据成员a,则应该像下面那样写输出语句: cout Derived2:a n;,7.5.4 虚基类,为解决在多重继承环境中因公共基类而带来的二 义性问题,C+语言提供了虚基类(虚拟基类)机制。 为了把一个基类定义为虚基类,必须在定义派生 类时在父类表中虚基类名字前加上关键字Virtual。声 明虚基类的一般格式为: class 派生类名:Virtual 访问权修饰符 父类名 . ;,面向对象程序设计 第二版,#include

28、 ”iostream.h” class Base protected: int a; public: Base(int i) a=i; ; class Derived1:virtual public Base int d1; public: Derived1(int p1,int p2):Base(p1) d1=p2; ; (续下页),面向对象程序设计 第二版,class Derived2:virtual public Base int d2; public: Derived2(int x1,int x2):Base(x1) d2=x2; ; class DD:Derived1,Derived

29、2 public: DD(int i1,int i2,int i3,int i4):Derived1(i1,i2), Derived2(i3,i4),Base(i1) void display() cout a n; ;,面向对象程序设计 第二版,面向对象程序设计 第二版,(a) 未使用虚基类,图7.4 示例程序的类等级,图(b) 使用虚基类,面向对象程序设计 第二版,由于在派生类DD的实例中只有基类Base中定 义的数据成员a的一份拷贝,因此,使用虚基类不 仅能消除公共基类可能带来的二义性问题,而且 能节省内存空间。当然,如果在解决实际应用问 题时,在派生类对象中确实需要公共基类中定义 的数

30、据成员的多份拷贝,则不宜使用虚基类。此 外,虚基类增加了系统的时间开销,使用虚基类 机制时应该考虑这个因素。非必要时不要过多地 使用虚基类。,面向对象程序设计 第二版,在使用虚基类机制时应该注意以下几点: 必须在最新派生出来的派生类的初始串列中,调用虚基类的构造函数,以初始化在虚基类中定义的数据成员。 初始串列中各个基类构造函数的调用顺序是,先调用虚基类构造函数,然后调用非虚基类构造函数。例如,在本节给出的示例程序中,在执行DD类的构造函数时,先调用虚基类Base的构造函数,然后再调用普通基类Derived1和Derived2的构造函数。 虚基类的构造函数仅调用一次,虚基类的构造函数由最新派生

31、出来的派生类负责调用。 如果在最新派生出来的派生类的初始串列中,没有显式调用虚基类构造函数,则编译程序将调用虚基类的缺省构造函数。,7.6 基类与派生类之间的转换,在满足一定的限定条件的前提下,C+语言可以自动地把一个派生类对象隐式地转换成一个基类对象。 可以在程序中用派生类对象给基类对象赋值。在这种情况下,实际上是用派生类对象从其基类继承来的数据成员的值,给基类对象的相应数据成员赋值 。 派生类对象拥有的数据成员,在基类对象中并不一定都有。如果使用基类对象给派生类对象赋值,则赋值后派生类对象的某些数据成员的值将是无意义的。因此, C不允许使用基类对象给派生类对象赋值。,面向对象程序设计 第二

32、版,7.6.1 赋值兼容规则,首先,必须强调指出的是,能够自动转换的 前提条件是,派生类从其基类公有派生。例如, 下面定义的两个类: class Base.; class Derived : public Base.; 则派生类Derived的对象具备自动转换成其基 类Base的对象的条件。,面向对象程序设计 第二版,下列几种情况是合法的: 可以用派生类对象给基类对象赋值。 Base b; Derived d; b=d; 可以用派生类对象来初始化基类的引用。 Derived d; Base ,面向对象程序设计 第二版,可以把派生类对象的地址赋值给指向基类的指针。 Derived d; Base *bptr=,在程序设计中应用赋值兼容规则时必须清楚,对象(或指向对象的指针)所能访问的成员,

温馨提示

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

评论

0/150

提交评论