Presentation is loading. Please wait.

Presentation is loading. Please wait.

第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院.

Similar presentations


Presentation on theme: "第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院."— Presentation transcript:

1 第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院

2 面向对象的继承技术提供了实现上述目标的有力手段。
C++的类提供了良好的模块分解技术,也具有可重用软件所期望的品质:它们是相似一致的模块,通过信息隐藏,将它们的接口和实现清楚地分开来。但是,仅有这些是不够的,我们还希望在类的基础上能取得更好的可重用性和可扩充性的目标。 面向对象的继承技术提供了实现上述目标的有力手段。 2019/1/17

3 派生类的概念 为什么要使用继承 在自然界中,继承这个概念是非常普遍的。小猫仔继承了猫爸猫妈的特性,所以长得是猫鼻子猫眼,我们不会把它错认为是小狗。继承就是这样,会将一些本质的特性遗传给子代,使子代在很大程度上具有与父代相同的性质。当然,子代同时还具有父代没有的特性。 2019/1/17

4 从上面的例子可以看出,一旦指定了某种事物父代的本质特征,那么它的子代将会自动具有那些性质。这就是一种朴素的可重用的概念。而且子代可以拥有父代没有的特性,这是可扩充的概念。
2019/1/17

5 我们来看一个现实世界中的例子: Rectangle Square Diamond Parallelogram Quadrangle 四边形是一个基本的概念,矩形、正方形、菱形和平行四边形都是派生的概念,因为它们都属于四边形,它们继承了四边形的所有性质,同时又扩充出各自的特性。 2019/1/17

6 C++的继承关系 C++中所谓的继承就是在一个已经存在的类的基础上建立另一个新的类。已存在的类称为“基类”或“父类”,新建立的类称为“派生类”或“子类”,如图所示: 基类 派生类 继承关系 派生类继承了基类的所有数据成员和成员函数,并增加了自己新的成员。 2019/1/17

7 单继承和多继承 C++中的继承又分为单继承和多继承: 单继承——派生类只有一个直接基类 多继承——派生类有多个直接基类 A B C 单继承
X Y Z 多继承 2019/1/17

8 单继承派生类 定义单继承派生类的语法格式:
class 派生类名 : <继承方式> 基类名{ ……//派生类新添加的成员 }; “继承方式”可以是公有继承(public)、私有继承(private)、以及保护继承( potected)。 虽然继承了基类的所有成员,但是派生类并非都能访问基类的所有成员,继承方式会影响派生类对基类中各种成员的引用。 2019/1/17

9 公有派生类 在声明派生类时将继承方式指定为public时,该类称为基类的公有派生类。 class 派生类名 : public 基类名 {
……//派生类新添加的成员 }; 2019/1/17

10 基类的成员在公有派生类中的引用权限 基类 公有成员 私有成员 保护成员 公有派生类 不可访问成员 公有派生时,基类的公有成员和保护成员仍然成为派生类的公有成员和保护成员;而基类的私有成员成为派生类的“不可访问成员”,不能被派生类直接引用。 不可访问成员:在类内和类外都不能被直接访问。 2019/1/17

11 举例 void main( ){ Derived obj(5,6); obj.v2=8; } class Base{ int v1;
public: int v2; Base(int a=0,int b=0){ v1=a;v2=b; } }; class Derived : public Base{ int v3; public: int v4; Derived(int a=0,int b=0){ v3=a;v4=b; } void func(){ int sum1=v1+v2+v3+v4;//错误 int sum2=v2+v3+v4; }; void main( ){ Derived obj(5,6); obj.v2=8; } 2019/1/17

12 私有派生类 在声明派生类时将继承方式指定为private时,该类称为基类的私有派生类。 class 派生类名 : private 基类名 {
……//派生类新添加的成员 }; 2019/1/17

13 基类的成员在私有派生类中的引用权限 基类 公有成员 私有成员 保护成员 私有派生类 不可访问成员 私有派生时,基类的公有成员和保护成员成为派生类的私有成员;而基类的私有成员成为派生类的“不可访问成员”,只有基类的成员函数可以去引用它。 2019/1/17

14 举例 void main( ){ Derived obj(5,6); obj.v2=8;//错误 } class Base{ int v1;
public: int v2; Base(int a=0,int b=0){ v1=a;v2=b; } }; class Derived : private Base{ int v3; public: int v4; Derived(int a=0,int b=0){ v3=a;v4=b; } void func(){ int sum1=v1+v2+v3+v4;//错误 int sum2=v2+v3+v4; }; void main( ){ Derived obj(5,6); obj.v2=8;//错误 } 2019/1/17

15 保护成员 虽然派生类继承了基类所有的特性(即成员),但是基类的私有成员对于派生类来说是不可见的,因此不能被派生类所访问。
为了解决这个问题,C++专门设置了保护成员,即用protected关键字说明的成员。保护成员除了能够被基类和派生类所访问以外,它和私有成员一样,其他类和类外都不能访问。 2019/1/17

16 举例 void main( ){ Derived obj(5,6); obj.v1=8;//错误 } class Base{
protected: int v1; public: int v2; Base(int a=0,int b=0){ v1=a;v2=b; } }; class Derived : public Base{ int v3; public: int v4; Derived(int a=0,int b=0){ v3=a;v4=b; } void func(){ int sum1=v1+v2+v3+v4;//正确 int sum2=v2+v3+v4; }; void main( ){ Derived obj(5,6); obj.v1=8;//错误 } 2019/1/17

17 保护派生类 在声明派生类时将继承方式指定为protected时,该类称为基类的保护派生类:
class 派生类名 : protected 基类名 { ……//派生类新添加的成员 }; 2019/1/17

18 基类的成员在保护派生类中的引用权限 保护派生时,基类的公有成员和保护成员成为派生类的保护成员;而基类的私有成员成为派生类的“不可访问成员”。
一般很少使用保护派生方式。 2019/1/17

19 小结 不同的继承方式,子类对基类中的成员的访问权限如下表所示: 基类 公有成员 私有成员 保护成员 公有派生类 不可访问成员 保护派生类
私有派生类 2019/1/17

20 派生类与基类同名成员的访问方式 C++允许派生类可以重新定义基类的成员,此时称派生类的成员覆盖了基类的同名成员。
如果在派生类中,或通过派生类对象来使用基类的同名成员,则可以显式地使用类名限定符,方式如下: 基类名::成员 2019/1/17

21 举例 class Base{ protected: int v1; public: int v2;
Base(int a=0,int b=0){ v1=a;v2=b; } }; class Derived : public Base{ int v2; public: int v3; Derived(int a=0,int b=0){ v2=a;v3=b; } void func(){ //使用的是Derived中的v2 int sum1=v1+v2+v3; //使用的是Base中的v2 int sum2=v1+Base::v2+v3; }; void main( ){ Derived obj(5,6); obj.v2=8;//错误 obj.Base::v2=9; } 2019/1/17

22 总结 当从现成的类派生出新类时,派生类将继承基类所有的特性(成员),还可以有以下功能:
增加新的成员 重新定义成员 从编码的角度来看,派生类从基类中以较低的代价换来了较大的灵活性。一旦产生了可靠的基类,只需要调试派生类中所作的修改即可。派生类可以对继承的属性进行扩展、限制或改变。 2019/1/17

23 赋值兼容规则 在公有派生方式下,派生类对象可以作为基类对象来使用,具体方式如下: 需要思考的两个问题: 派生类的对象可以直接赋值给基类的对象
基类对象的引用可以引用一个派生类对象 基类对象的指针可以指向一个派生类对象 需要思考的两个问题: 为什么是“派生类对象->基类对象”,而不是“基类对象->派生类对象”? 其他继承方式会有以上规则吗? 2019/1/17

24 解决第一个问题 以上规则的具体使用方式: 基类对象和派生类对象的内存布局方式 Base Bobj; Derived Dobj;
Bobj=Dobj; Base &refB=Dobj; Base *pB=&Dobj; 基类对象和派生类对象的内存布局方式 v1 v2 v3 v4 基类对象 派生类对象 2019/1/17

25 当一个派生类对象直接赋值给基类对象时,很明显地,不是所有的数据都赋给了基类对象,赋予的只是派生类对象的一部分。这部分叫做派生类类对象的“切片”(sliced)。
2019/1/17

26 解决第二个问题 回忆一下不同的继承方式,子类对基类中的成员的访问权限:
公有成员 私有成员 保护成员 公有派生类 不可访问成员 私有派生类 保护派生类 只有在公有派生的情况下,才有可能出现“基类的公有成员变成派生类的公有成员”的情况。 2019/1/17

27 派生类对象的初始化 派生类对象包含了从基类继承过来的数据。如果基类有自定义的构造函数或有带参数的构造函数,那么如何调用基类的构造函数来初始化基类数据呢?C++采用如下方式来定义派生类的构造函数: 派生类构造函数(参数表):基类构造函数(参数表),对象成员(参数表) { ……//初始化自定义数据成员 } 如果基类使用的是缺省的构造函数或不带参数的构造函数,那么在初始化列表中可以省略“基类构造函数(参数表)”这一项。 2019/1/17

28 构造函数的调用顺序如下: 先调用基类构造函数; 再调用对象成员所属类的构造函数; 最后调用派生类构造函数; 析构函数的调用顺序如下:
先调用派生类的析构函数; 再调用对象成员所属类的析构函数; 最后调用基类的析构函数; 2019/1/17

29 举例 class Base{ public: Base() { cout<<“Base created\n”;} };
class Derived:public Base{ Derived(){ cout<<“Derived created\n”; } void main() { Derived d; } 2019/1/17

30 请分析 此时基类的数据成员x的值为多少? class base{ int x; public: base(int i):x(i){
cout<<"base\n"; } }; class derived:public base{ int a; public: derived(int i):a(i*4),base(a){ cout<<"derived\n"; } }; 此时基类的数据成员x的值为多少? 2019/1/17

31 多继承派生类 派生类有多个直接基类,这叫多继承。 Quadrangle Diamond Square Rectangle 2019/1/17

32 class 派生类名 : <继承方式1> 基类名1, <继承方式2> 基类名2, …… { };
定义多继承派生类的语法格式: class 派生类名 : <继承方式1> 基类名1, <继承方式2> 基类名2, …… { ……//派生类新添加的成员 }; 2019/1/17

33 多继承派生类的构造函数: 派生类名(参数表):基类名1(参数表1), 基类名2(参数表2), 对象成员(参数表3), … { //派生类构造函数 } 执行顺序:先执行所有基类的构造函数(按这些基类被继承时声明顺序依次执行基类1、基类2的构造函数),再执行对象成员所属类的构造函数,最后执行派生类本身的构造函数。 2019/1/17

34 多继承中的二义性问题 在多继承中,一个类不可以重复成为另一个类的直接基类,但可以多次成为间接基类。此时派生类访问基类成员时可能出现二义性。下面是出现二义性的两种情况: 访问不同基类的具有相同名字成员时可能出现二义性。 访问共同基类的成员时可能出现二义性。 2019/1/17

35 访问不同基类同名成员时的二义性 class C: public A, public B{ public: void g();
class A { public: int value; void f(); }; class B void g(); class C: public A, public B{ public: void g(); void h(); }; Void main() { C C1; C1.f(); //出现二义性 C1.value=9; //出现二义性 } 访问的f()或value是类A中的还是类B中的呢?解决办法是用类名对成员加以限定,例如: C1.A::f(); 或者C1.B::f(); C1.A::value 或者C1.B::value 2019/1/17

36 访问共同基类成员时的二义性 class A{ public: int a; void g( ); }; class B1: public A{ int b1; class B2: public A{ int b2; class C: public B1, public B2{ int c; public: int f(); }; void main(){ C Cobj; Cobj.a=8; //有二义性 Cobj.A::a=9; //有二义性 Cobj.g( ); //有二义性 } 在“Cobj.a;”中是对B1 继承的基类A 的成员a,还是对B2继承基类A 的成员a 进行访问呢?故有二义性。消除二义性的方法可改写为:Cobj.B1::a;或 Cobj.B2::a; 2019/1/17

37 以上4个类的关系如下图所示: A(a) B1(b1) B2(b2) C(f(),c)
从图中看出,类A 是派生类C 两条继承路径上的一个公共基类,因此这个公共基类会在派生类对象中产生两个基类子对象。虽然可以通过类限定符的方式避免二义性,但问题的关键在于我们不需要在派生类对象中存在多个基类对象的拷贝!如果要使这个公共基类在派生类中只产生一个基类子对象,则需要将这个基类设置为虚基类。 2019/1/17

38 虚基类 引进虚基类的目的是为了解决二义性问题,使公共基类在其派生类对象中只产生一个基类子对象。 虚基类说明格式如下:
virtual <继承方式><基类名> 就是在“继承方式”前面用关键字virtual来说明基类。 2019/1/17

39 例子:虚基类的使用 class A{ public: int a; void g( ); };
class B1: virtural public A{ int b1; class B2: virtural public A{ int b2; class C: public B1, public B2{ int c; public: int f(); }; void main(){ C Cobj; Cobj.a=8; //或Cobj.A::a=9; Cobj.g( ); } 2019/1/17

40 和单继承中的公有派生类一样,可以进行如下赋值操作: A &ref=Cobj; A *p=&Cobj; A Aobj=Cobj;
例中,类A,类B1,类B2 和类C之间的关系如下图所示。图中可见,虚基类子对象被合并成一个子对象,这种“合并”作用,使得可能出现的二义性被消除。 A(g(),a) B1(b1) B2(b2) C(f(),c) 和单继承中的公有派生类一样,可以进行如下赋值操作: A &ref=Cobj; A *p=&Cobj; A Aobj=Cobj; 2019/1/17

41 请比较下面两种情况 对于类C而言,类A是类B2的“假”基类,是类B1的“真”基类。
class C: public B1, public B2{ int c; public: int f(); }; class C: public B2, public B1{ int c; public: int f(); }; 对于类C而言,类A是类B2的“假”基类,是类B1的“真”基类。 对于类C而言,类A是类B1的“假”基类,是类B2的“真”基类。 因此,虚基类只是一个相对的概念。 2019/1/17

42 虚基类对象的初始化 虚基类构造函数的调用次序如下: 虚基类的构造函数在非虚基类之前调用。
若在同一层次中包含多个虚基类,那么虚基类构造函数按它们说明的次序调用。 若虚基类由非虚基类派生,则遵守先调用基类构造函数,再调用派生类构造函数的规则。 2019/1/17

43 例子:5-22 当声明toplevel类的对象时,构造函数的调用次序为:
class base{…}; class bas2{…}; class level1 : public base2, vitrual public base{…}; class level2 : public base2, vitrual public base{…}; class toplevel : public level1, vitrual public level2{…}; 当声明toplevel类的对象时,构造函数的调用次序为: base base2 level2 base2 level1 toplevel 2019/1/17


Download ppt "第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院."

Similar presentations


Ads by Google