《Cchapter4课件》由会员分享,可在线阅读,更多相关《Cchapter4课件(38页珍藏版)》请在金锄头文库上搜索。
1、第第4 4章章 继承性与派生类继承性与派生类n本章的教学目的本章的教学目的 C+面向对象程序设计提供了继承机制。即面向对象程序设计提供了继承机制。即C+面向程序设计允许将具有共性的事物抽象成类,面向程序设计允许将具有共性的事物抽象成类,并允许利用已有类定义新的类,将类组织成合理的并允许利用已有类定义新的类,将类组织成合理的层次结构,以精确地描述客观事物之间的联系。层次结构,以精确地描述客观事物之间的联系。 C+的这种继承机制为编程人员提供了重复利用程的这种继承机制为编程人员提供了重复利用程序资源的一种途径,编程人员可以扩充和完善旧的序资源的一种途径,编程人员可以扩充和完善旧的程序以适用新的需求
2、,大大提高程序的开发效率。程序以适用新的需求,大大提高程序的开发效率。因此,我们在前面学习了类的定义和构造设计的基因此,我们在前面学习了类的定义和构造设计的基础上,应该进一步研究类之间的关系,特别地应该础上,应该进一步研究类之间的关系,特别地应该学习类之间的继承性和派生性,以全面掌握学习类之间的继承性和派生性,以全面掌握C+面面向对象程序设计方法。向对象程序设计方法。1C+chapter4 本章的教学内容本章的教学内容继承的概念继承的概念派生类的定义派生类的定义 派生类的构造函数和析构函数派生类的构造函数和析构函数继承属性的访问权限控制继承属性的访问权限控制多继承的概念多继承的概念 2C+ch
3、apter4n类的继承:类的继承:就是根据就是根据一个类创建一个新类一个类创建一个新类的过程。的过程。n类的派生:类的派生:从已有类从已有类产生新类的过程就是产生新类的过程就是类的派生。类的派生。n通常将用来派生新类通常将用来派生新类的类称为的类称为基类基类(或(或父父类类),而将派生出来而将派生出来的新类称为的新类称为派生类派生类 (或(或子类子类)。 n新类自动具有已有类新类自动具有已有类的所有成员,并可根的所有成员,并可根据需要添加更多的成据需要添加更多的成员。员。 4.1 继承、派生和类的层次关系继承、派生和类的层次关系3C+chapter44.2 派生类的定义方法派生类的定义方法 n
4、派生类派生类是在继承基类的属性和是在继承基类的属性和操作的基础上增添新的属性和操作的基础上增添新的属性和操作而产生的新类。操作而产生的新类。n派生类是基类的特殊子类,基派生类是基类的特殊子类,基类是抽取派生类的主要属性和类是抽取派生类的主要属性和操作而得到的抽象描述。操作而得到的抽象描述。虽然虽然派生类继承了基类的所有特性,派生类继承了基类的所有特性,但不等同于基类,否则就没有但不等同于基类,否则就没有派生的必要了。派生的必要了。n继承关系体现了继承关系体现了特殊与一般的特殊与一般的关系。关系。 4C+chapter4n继承的一个作用继承的一个作用就是就是允许派生类在继承父允许派生类在继承父类
5、共性的基础上,增类共性的基础上,增加新的属性和操作来加新的属性和操作来实现特殊功能;实现特殊功能;另一另一个作用是代码重用个作用是代码重用,从基类派生子类,子从基类派生子类,子类无需修改基类的代类无需修改基类的代码,就可以直接拥有码,就可以直接拥有基类的成员,然后增基类的成员,然后增加少量代码就可以实加少量代码就可以实现特殊功能,这就实现特殊功能,这就实现了代码的重用。现了代码的重用。5C+chapter4class : ; 其中,其中,有有三种:公有继承、私三种:公有继承、私有继承和保护继承,有继承和保护继承,分别用关键字分别用关键字public、private和和protected表示。缺
6、省情况下为表示。缺省情况下为私有继承。私有继承。class person /基类基类protected: char name11; char sex; int age; public: char* GetName(); int GetSex(); int Getage(); ; class Student:public personprivate: char id9; float score; public: float GetScore(); ;4.2.1 派生类的定义派生类的定义6C+chapter4【例例4.1】 派生类的定义派生类的定义 class A /定义一个基类定义一个基类A,A
7、也称为超类、父类也称为超类、父类 int i; public: void set_i(int n) i=n; int get_i( ) return i; ; class B : public A /下划线处说明类下划线处说明类B将继承类将继承类A的公有成员的公有成员int j; public:void set_j(int n) j=n;int Multiply( ) return j*get_i( ) ; /可调用基类的可调用基类的get_i( ); /B不能访问不能访问A类的私有成员类的私有成员i。Main ( ) B ob;ob.set_i(10); /初始化初始化ii,B可通过可通过A
8、类的类的set_i( )访问访问iob.set_j(4); /初始化初始化B中的中的j coutob. Multiply( );/运行结果为运行结果为40return 0;7C+chapter4n派生类的生成包含派生类的生成包含三个步骤:三个步骤:(1 1)吸收基类成员)吸收基类成员 吸收基类的部分成吸收基类的部分成员员, ,不吸收构造函数不吸收构造函数和析构函数。它是和析构函数。它是一个重用过程。一个重用过程。(2 2)改造基类成员)改造基类成员 一是通过派生类定一是通过派生类定义时的继承方式来义时的继承方式来控制;控制; 二是通过定义同名二是通过定义同名成员屏蔽基类成员。成员屏蔽基类成员。
9、它是一个扩充过程。它是一个扩充过程。4.2.2 派生类的生成过程派生类的生成过程 class person /基类基类 protected: char name11; char sex; int age; public: void Show(); ; class Student:public personprivate: float score; public: void Show() /改造基类成员改造基类成员 person:Show(); coutscoreendl;8C+chapter4(3 3)添加派生类新成员)添加派生类新成员 仅仅继承基类的成仅仅继承基类的成员是不够的,需要在员是不
10、够的,需要在派生类中添加新成员,派生类中添加新成员,以保证派生类在功能以保证派生类在功能上有所发展。同基类上有所发展。同基类的构造函数和析构函的构造函数和析构函数是不能被继承的,数是不能被继承的,需要加入新的构造函需要加入新的构造函数和析构函数数和析构函数完成一完成一些特别的初始化和扫些特别的初始化和扫尾清理工作。尾清理工作。class person /基类基类 protected: char name11; char sex; int age; public: void Show(); ; class Student:public personprivate: float score; pu
11、blic: void Show() person:Show(); coutscoreendl;9C+chapter4 4.3 4.3 基类成员的访问权限控制基类成员的访问权限控制 从派生类的定义格式可知,有三种继承从派生类的定义格式可知,有三种继承方式:方式:公有公有、私有私有和和保护保护。因此,派生类。因此,派生类对基类成员的访问权限控制也从三个方面对基类成员的访问权限控制也从三个方面考虑:考虑: (1)(1)公有继承公有继承的访问权限控制的访问权限控制; ; (2) (2)私有继承私有继承的访问权限控制的访问权限控制; ; (3) (3)保护继承保护继承的访问权限控制的访问权限控制; ;1
12、0C+chapter44.3.1 公有继承的访问权限控制公有继承的访问权限控制 当类的继承方式为当类的继承方式为公有继承时公有继承时:(1)在派生类中,基类的公有成员和保护成员在派生类中,基类的公有成员和保护成员被继承后仍然作为派生类的公有成员和保护被继承后仍然作为派生类的公有成员和保护成员,派生类的成员函数可以直接访问它们成员,派生类的成员函数可以直接访问它们;(2)基类的私有成员无法继承为派生类的私有基类的私有成员无法继承为派生类的私有私有成员或其他成员,因此派生类的成员函私有成员或其他成员,因此派生类的成员函数无法直接访问基类的私有成员。数无法直接访问基类的私有成员。(3)在类外,派生类
13、的对象只可以访问继承下在类外,派生类的对象只可以访问继承下来的基类公有成员。来的基类公有成员。11C+chapter4【例例4.1】公有继承的访问权限控制公有继承的访问权限控制class A /定义一个基类定义一个基类A,A也称为超类、父类也称为超类、父类 int i; public: void set_i(int n) i=n; int get_i( ) return i; ; class B : public A /下划线处说明类下划线处说明类B将继承类将继承类A的公有成员的公有成员int j; public:void set_j(int n) j=n;int Multiply( ) re
14、turn j*get_i( ) ; /可调用基类的可调用基类的get_i( ); /B不能访问不能访问A类的私有成员类的私有成员i。Main ( ) B ob;ob.set_i(10); /初始化初始化i,B可通过可通过A类的类的set_i( )访问访问iob.set_j(4); /初始化初始化B中的中的j coutob. Multiply( );/运行结果为运行结果为40return 0;12C+chapter4n当类的继承方式为当类的继承方式为私有继承时私有继承时: (1)在派生类中,基类的公有成员和保护成员被继在派生类中,基类的公有成员和保护成员被继承以后将作为派生类的私有成员,派生类的
15、成员承以后将作为派生类的私有成员,派生类的成员函数可以直接访问它们函数可以直接访问它们; (2)基类的私有成员没有被继承过来,因此派生类基类的私有成员没有被继承过来,因此派生类的成员函数无法访问基类的私有成员的成员函数无法访问基类的私有成员; (3)在类外,无法访问派生类对象中从基类继承的在类外,无法访问派生类对象中从基类继承的所有成员。所有成员。 (4) 私有继承之后,基类的成员再也无法在以后私有继承之后,基类的成员再也无法在以后的派生类中发挥作用,出于这种原因,一般不使的派生类中发挥作用,出于这种原因,一般不使用私有继承方式。用私有继承方式。4.3.2 私有继承的访问权限控制私有继承的访问
16、权限控制 13C+chapter4例例4-2 私有继承举例私有继承举例 #include #include class Person private: char name11; char sex; protected: int age; public: Person(const char* Name,int Age,char Sex); char* GetName() return name; int Getage(); int GetSex; ;14C+chapter4class Student:private Personprivate: char id9; float score; pu
17、blic: Student(char* pName,int Age,char Sex, char* pId,float Score) void Display(); void Student:Display() cout“name:”GetName()t /访问变为私有的基类成访问变为私有的基类成 员函数员函数 cout“id:”idt; /直接访问本类私有成员直接访问本类私有成员 cout“age:”aget; /访问基类的保护成员访问基类的保护成员(变为私有的变为私有的) cout“score:”scoreendl; void main() Student s2(“wang min”,20
18、,m,”03410102”,80); s2.Display(); 15C+chapter44.3.3 保护继承的访问权限控制保护继承的访问权限控制 当类的继承方式为当类的继承方式为保护继承时保护继承时: :(1)(1)在派生类中,基类的公有成员和保护成员被继承以在派生类中,基类的公有成员和保护成员被继承以后将作为派生类的保护成员,派生类的成员可以直接后将作为派生类的保护成员,派生类的成员可以直接访问它们访问它们; ;(2)(2)基类的私有成员没有被继承,因此派生类的成员无基类的私有成员没有被继承,因此派生类的成员无法访问基类的私有成员。法访问基类的私有成员。(3)(3)在类外,无法访问派生类对
19、象中从基类继承的所有在类外,无法访问派生类对象中从基类继承的所有成员。成员。(4)(4)与私有继承不同的是与私有继承不同的是, ,保护继承还没有完全终止基类保护继承还没有完全终止基类的功能的功能, ,即保护继承可以传递部分基类成员给派生类即保护继承可以传递部分基类成员给派生类的派生类的派生类, ,而私有继承不可以。如果合理地利用保护而私有继承不可以。如果合理地利用保护继承继承, ,就可以在类的复杂层次关系中为共享访问与成就可以在类的复杂层次关系中为共享访问与成员隐蔽之间找到一个平衡点员隐蔽之间找到一个平衡点, ,既实现部分成员隐蔽既实现部分成员隐蔽, ,又又能方便部分成员的继承,实现代码的高效
20、重用和扩充。能方便部分成员的继承,实现代码的高效重用和扩充。16C+chapter4n不论哪种继承方式,派生类新定义成员均不能直接不论哪种继承方式,派生类新定义成员均不能直接访问基类的私有成员,只能通过基类的公有成员函访问基类的私有成员,只能通过基类的公有成员函数或保护成员函数访问基类的私有数据成员,而基数或保护成员函数访问基类的私有数据成员,而基类的私有成员函数根本就不会继承,更谈不上使用。类的私有成员函数根本就不会继承,更谈不上使用。所以,除非仅限于本类使用,否则,一般不将成员所以,除非仅限于本类使用,否则,一般不将成员函数定义为私有成员。函数定义为私有成员。17C+chapter4n在定
21、义派生类的构造函数时除了对自己的数据成员进在定义派生类的构造函数时除了对自己的数据成员进行初始化外行初始化外, ,还必须调用基类的构造函数初始化基类的数还必须调用基类的构造函数初始化基类的数据成员。如果派生类中还有对象成员时,还应调用对象据成员。如果派生类中还有对象成员时,还应调用对象成员类的构造函数初始化对象成员。成员类的构造函数初始化对象成员。 派生类构造函数的一般格式如下:派生类构造函数的一般格式如下: ():(),, (), , ), , () ; ;5.4.1 派生类的构造函数派生类的构造函数 18C+chapter4 例例4-3 派生类构造函数定义举例派生类构造函数定义举例 #in
22、clude #include class Person private: char name11; char sex; protected: int age; public: Person(const char* Name,int Age,char Sex); char* GetName() return (name); int Getage(); int GetSex; ;19C+chapter4class Student:public Personprivate: char id9; float score; public: Student(char* pName,int Age,char
23、 Sex, char* pId, float Score): person(pName, Age, Sex) strcpy(id,pId); score=Score; char* GetId(char* pId) return (id); float GetScore() return score; void Display(); 20C+chapter4使用使用派生类构造函数时应注意派生类构造函数时应注意: : (1) (1)当基类中没有显式定义构造函数时当基类中没有显式定义构造函数时, ,派生类构造函数的定义可以省略对基类派生类构造函数的定义可以省略对基类构造函数的调用构造函数的调用, ,
24、而采用隐含调用。而采用隐含调用。(2 2)当基类的构造函数使用一个或多个)当基类的构造函数使用一个或多个参数时,派生类必须定义构造函数,提参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。供将参数传递给基类构造函数的途径。这时,派生类构造函数的函数体可能为这时,派生类构造函数的函数体可能为空,仅起到参数传递的作用。空,仅起到参数传递的作用。21C+chapter4例例4-4 4-4 派生类构造函数显式调用基类构造函数的调用顺序。派生类构造函数显式调用基类构造函数的调用顺序。#include class A public: A() cout“A Constructor1”end
25、l; A(int i) x1=i;cout“A Constructor2”endl; void dispa() cout“x1=“x1endl; private: int x1; ; class B:public A public: B() cout“B Constructor1”endl; B(int i):A(i+10) x2=i; cout“B Constructor2”endl; void dispb() dispa(); /调用基类成员函数调用基类成员函数 cout“x2=“x2endl; private: int x2; ; void main() B b(2); b.dispb(
26、); 运行结果运行结果 A Constructor2 B Constructor2 x1=12 x2=222C+chapter44.4.2 派生类的析构函数派生类的析构函数n由于基类的析构函数也不能被继承,因此,由于基类的析构函数也不能被继承,因此,必须定义派生类的析构函数。派生类的必须定义派生类的析构函数。派生类的析构函数还必须通过调用基类的析构函析构函数还必须通过调用基类的析构函数来做基类的一些清理工作。数来做基类的一些清理工作。n调用顺序是调用顺序是:先调用派生类的析构函数先调用派生类的析构函数;再调用对象成员类的析构函数(如果有再调用对象成员类的析构函数(如果有对象成员)对象成员);最
27、后调用基类的析构函数,其顺序与调最后调用基类的析构函数,其顺序与调用构造函数的顺序相反。用构造函数的顺序相反。 23C+chapter4例例4-8继承方式下构造函数和析构函数的调用顺序继承方式下构造函数和析构函数的调用顺序#include class A public: A() cout“A Constructor”endl; A() cout“A Destructor”endl; class B:public A public: B() cout“B Constructor”endl; B() cout“B Destructor”endl; void main() B b; 运行结果运行结果
28、: A Constructor B Constructor B Destructor A Destructor24C+chapter44.5 多继承多继承n根据派生类继承基类的个数,将继承分为根据派生类继承基类的个数,将继承分为单继承单继承和和多多继承继承。n当派生类只有一个基类时称为当派生类只有一个基类时称为单继承单继承, ,如图如图(a)(a)和和(b)(b)所所示示; ;当派生类有多个基类时称为当派生类有多个基类时称为多继承多继承, ,如图如图(c)(c)所示所示。n单继承可以看作是多继承的一个特例,多继承可以看单继承可以看作是多继承的一个特例,多继承可以看作是多个单继承的组合,它们有很
29、多相同特性。作是多个单继承的组合,它们有很多相同特性。25C+chapter44.5.1 多继承的定义格式多继承的定义格式 多继承可以看作是单继承的扩展,派生类与每个基类之间的关多继承可以看作是单继承的扩展,派生类与每个基类之间的关系可以看作是一个单继承。在系可以看作是一个单继承。在C+C+中,多继承的定义格式如下:中,多继承的定义格式如下: class class : 1, n ; ;class window public: window(int top,int left,int bottom,int right);class scrollbar public:scrollbar(int t
30、op,int left,int bottom,int right);class scrollbarwind : public window , public scrollbar public:scrollbarwind(int top ,int left ,int bottom ,int right);26C+chapter4在多继承方式下,派生类构造函数的定义格式如下在多继承方式下,派生类构造函数的定义格式如下:4.5.2 多继承的构造函数多继承的构造函数 (): (), () , , (), ), ,() ) ;构造函数的调用顺序构造函数的调用顺序:先调用所有基类的构造函数初始化所有基类的
31、数据成员。先调用所有基类的构造函数初始化所有基类的数据成员。处于处于同一层次的各基类构造函数的调用顺序取决于定义同一层次的各基类构造函数的调用顺序取决于定义派生类时所指定的基类顺序派生类时所指定的基类顺序,与派生类构造函数中所定,与派生类构造函数中所定义的成员初始化列表顺序无关。义的成员初始化列表顺序无关。如果派生类中还有对象成员时,还应调用对象成员类的如果派生类中还有对象成员时,还应调用对象成员类的构造函数初始化对象成员构造函数初始化对象成员。27C+chapter4例例4-9 多继承方式下构造函数和析构函数的调用顺序。多继承方式下构造函数和析构函数的调用顺序。#include class
32、A public: A(int i)a=i; cout“A Constructor”endl; void disp() cout“a=“aendl; A()cout“A Destructor”endl; private: int a; ;class B public: B(int j)b=j; cout“B Constructor”endl; void disp() cout“b=“bendl; B()cout“B Destructor”endl; private: int b; ;28C+chapter4class C: public B,pulic A public: C(int k):A
33、(k+2),B(k-2) c=k; cout“C Constructor”endl; void disp() A:disp(); B:disp(); cout“c=“cendl; c()cout“C Destructor”endl; private: int c; ;void main() C obj(10); obj.disp();运行结果:运行结果:B ConstructorB ConstructorA ConstructorA ConstructorC ConstructorC Constructora=12a=12b=8b=8c=10c=10C DestructorA Destruct
34、orB Destructor29C+chapter44.5.3 虚基类虚基类 在派生类中对基类成员的访问应该是唯一的。但是,在多继在派生类中对基类成员的访问应该是唯一的。但是,在多继承方式下,可能造成对基类中某个成员的访问出现不唯一的承方式下,可能造成对基类中某个成员的访问出现不唯一的情况,称为对基类成员访问的二义性问题情况,称为对基类成员访问的二义性问题, ,如下图所示。如下图所示。30C+chapter4n解决的办法解决的办法 (1)(1)用作用域运算符用作用域运算符“:”:”进行限定进行限定; ; (2) (2)还可以将直接基类的共同基类设置为还可以将直接基类的共同基类设置为虚虚基类基类
35、, ,则从不同路径继承过来的该类成员在则从不同路径继承过来的该类成员在内存中只拥有一个副本内存中只拥有一个副本, ,进而解决了同名成进而解决了同名成员的唯一标识问题。员的唯一标识问题。n虚基类的定义格式为虚基类的定义格式为:class :virtual 31C+chapter4#include class A public: A() a=10; protected: int a; ;class A1: virtual public A public: A1() coutaendl;class A2: virtual public A public: A2() coutaendl;class B
36、: A1,A2 public: B() coutaendl;void main() B obj; 32C+chapter4习题1. .写出下面程序的运行结果:写出下面程序的运行结果:#include Class Apublic: A(int i,int j)a=i;b=j) void move(int x,int y)a+=x;b+=y; void show()cout“(”a“,”b“)”endl; private: int a,b;33C+chapter4class B:public A public: B(int i,int j,int k,int l):A(i,j),x(k),y(l)
37、 void show()coutx“,”yendl; void fun() move(3,5); void f1() A:show(); private: int x,y; ;34C+chapter4void main() A aa(1,2); aa.show(); B bb(3,4,5,6); bb.fun(); bb.A:show(); bb.B:show(); bb.f1(); 35C+chapter42. 写出下面程序的运行结果,并分析总结写出下面程序的运行结果,并分析总结.#includeclass Basepublic: void who()cout“Base class”endl
38、;class Derive1:public Base public: void who() cout“Derive1 class”endl; ;class Derive2:public Base public: void who() cout“Derive2 class”who(); p=&obj2; p-who(); p=&obj3; p-who(); obj2.who(); obj3.who();37C+chapter43.3.编写一个学生和教师数据输入和显示程序。编写一个学生和教师数据输入和显示程序。学生数据有编号、姓名、班号和成绩,教师学生数据有编号、姓名、班号和成绩,教师数据有编号、姓名、职称和部门。要求设计数据有编号、姓名、职称和部门。要求设计这两个类的一个基类这两个类的一个基类person,person,使之包含编号和使之包含编号和姓名这两个属性和显示编号和姓名的操作函姓名这两个属性和显示编号和姓名的操作函数。(数。(实验题实验题)38C+chapter4