《C类的继承和派生PPT精品文档》由会员分享,可在线阅读,更多相关《C类的继承和派生PPT精品文档(57页珍藏版)》请在金锄头文库上搜索。
1、第四章继承和派生1.4.1继承和派生的概念继承和派生的概念面向对象程序设计有面向对象程序设计有4个主要特点个主要特点: 抽象抽象、封装封装、继承继承和和多态性多态性。在本章中主要介绍有关继承的知识,在下一章中。在本章中主要介绍有关继承的知识,在下一章中将介绍多态性。将介绍多态性。C+语言提供了类的继承机制,解决了软件重用问题。语言提供了类的继承机制,解决了软件重用问题。4.1.1 继承与派生的概念继承与派生的概念一个类中包含了若干数据成员和成员函数。在不同的类一个类中包含了若干数据成员和成员函数。在不同的类中,数据成员和成员函数是不相同的。但有时两个类的内中,数据成员和成员函数是不相同的。但有
2、时两个类的内容基本相同或有一部分相同。容基本相同或有一部分相同。2.类的继承:类的继承:一个新类从已存在的类那里获得该类已有的一个新类从已存在的类那里获得该类已有的特性叫作类的继承。已存在的类叫作特性叫作类的继承。已存在的类叫作父类父类,也叫作,也叫作基类基类。产生的新类叫作产生的新类叫作子类子类或或派生类派生类。类的派生:类的派生:从一个已有的类那里产生一个新类的过程叫从一个已有的类那里产生一个新类的过程叫类的派生。已存在的类叫作类的派生。已存在的类叫作父类父类,也叫作,也叫作基类基类。产生的新。产生的新类叫作类叫作派生类派生类或或子类子类。类的继承和派生是同一概念,前者是从子类的角度来说,
3、类的继承和派生是同一概念,前者是从子类的角度来说,后者是从父类的角度来说的。我们通常说子类继承了父类。后者是从父类的角度来说的。我们通常说子类继承了父类。父类派生了子类。父类派生了子类。3.描述各级学生的类的继承关系如下图:描述各级学生的类的继承关系如下图:基类与派生类的关系:基类与派生类的关系:u派生类是基类的具体化,基类则是派生类的抽象派生类是基类的具体化,基类则是派生类的抽象u一个派生类的对象也是一个基类的对象。应该具有基类一个派生类的对象也是一个基类的对象。应该具有基类的一切属性和方法。的一切属性和方法。u派生类除了具有基类的一切属性和方法外,还可以有自派生类除了具有基类的一切属性和方
4、法外,还可以有自己所特有的属性和方法。己所特有的属性和方法。4.1.2 派生类和基类的关系派生类和基类的关系4.4.1.3 单继承与多继承单继承与多继承单继承:单继承:一个派生类只从一个基类继承。一个派生类只从一个基类继承。多重继承:多重继承:一个派生类从两个或多个基类继承。一个派生类从两个或多个基类继承。5.4.2 派生类的声明方式派生类的声明方式声明派生类的一般形式为声明派生类的一般形式为class 派生类名派生类名: 继承方式继承方式 基类名基类名派生类新增加的成员派生类新增加的成员 ;继承方式包括继承方式包括: public(公有的公有的),private(私有的私有的)和和prote
5、cted(受保护的受保护的),此项是可选的,如果不写此项,则默,此项是可选的,如果不写此项,则默认为认为private(私有的私有的)。如下程序演示了类如下程序演示了类Rectangle(四边形四边形)由类由类Point继承而来。继承而来。6.void SetPoint(int x,int y)this-x=x;this-y=y;void MovePoint(int dx,int dy)x+=dx;y+=dy;void ShowPoint()cout(x,y);/Point.h#include #include using namespace std;class Pointprivate:in
6、t x,y;public:int GetX()return x;int GetY()return y;7.void SetRect(int x,int y,int w,int h)SetPoint(x,y);width=w;height=h;void ShowRect()cout左上角坐标为:;ShowPoint();coutendl;cout宽为:widthendl;cout长为:heightendl;/Rectangle.h#include #include Point.husing namespace std;class Rectangle:public Pointprivate:int
7、 width;int height;public:int GetWidth()return width;int GetHight()return height;8./main.cpp#include Rectangle.hvoid main()Rectangle r;r.SetRect(0,0,10,20);r.ShowRect();r.MovePoint(10,10);r.ShowRect();9.4.3 派生类的构成派生类的构成派生类中的成员包括从基类继承过来的成员和自己增加派生类中的成员包括从基类继承过来的成员和自己增加的成员两大部分。在基类中包括数据成员和成员函数的成员两大部分。在基类
8、中包括数据成员和成员函数(或称数据或称数据与方法与方法)两部分,派生类分为两大部分两部分,派生类分为两大部分: 一部分是从基类继承来一部分是从基类继承来的成员,另一部分是在声明派生类时增加的部分。每一部分均的成员,另一部分是在声明派生类时增加的部分。每一部分均分别包括数据成员和成员函数。分别包括数据成员和成员函数。10.如果在派生类中定义了和基类中同名函数(函数参数个数如果在派生类中定义了和基类中同名函数(函数参数个数和类型可以相同也可以不相同),则派生类中的函数会隐藏基和类型可以相同也可以不相同),则派生类中的函数会隐藏基类的同名函数。在派生类中不能直接访问基类中的同名函数。类的同名函数。在
9、派生类中不能直接访问基类中的同名函数。(注意与重载的区别。在同一个类中的同名不同参函数为重载(注意与重载的区别。在同一个类中的同名不同参函数为重载函数)函数)如程序如程序PointRect1所示:所示:11.4.4继承方式继承方式派生类的继承方式有三种:派生类的继承方式有三种:public,private,protected。不同的继承方式决定了基类成员在派生类中的访问属性。不同的继承方式决定了基类成员在派生类中的访问属性。继承方式继承方式基类中的访问属性基类中的访问属性派生类中的访问属性派生类中的访问属性publicpublicpublicprotectedprotectedprivate不
10、可访问不可访问privatepublicprivateprotectedprivateprivate不可访问不可访问protectedpublicprotectedprotectedprotectedprivate不可访问不可访问12.4.4.1类的保护成员类的保护成员前面介绍过类的成员前面介绍过类的成员( (数据成员和成员函数数据成员和成员函数) )的访问属性有的访问属性有私有的私有的(private)(private)的和公有的的和公有的(public(public的的) )。另外还提到类的访问。另外还提到类的访问属性也可以有保护的属性也可以有保护的(protected(protected
11、的的) )。类中的类中的protected成员与成员与private成员一样,只能在本类成员一样,只能在本类的成员函数中访问,不能在类外通过对象来访问。的成员函数中访问,不能在类外通过对象来访问。但通过上面的表中可以看出当类派生时,基类的但通过上面的表中可以看出当类派生时,基类的private成员成员在派生类中是不可访问的。而基类的在派生类中是不可访问的。而基类的protected成员在派生成员在派生类中随继承方式的不同而不同。类中随继承方式的不同而不同。13.class Drived:public Baseprotected:int j;public:void Fun()i=20;#incl
12、ude using namespace std;class Baseprotected:int i;public:void F();void main()Drived d;14.4.5 派生类的构造函数和析构函数派生类的构造函数和析构函数构造函数的主要作用是对数据成员初始化。构造函数的主要作用是对数据成员初始化。在设计派生类在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化还应当考虑基类的数据成员初始化。也就是说,希望在执行派。也就是说,希望在执行派生类的构造函数时,使派生类的数据成员和基类
13、的数据成员同生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是时都被初始化。解决这个问题的思路是: 在执行派生类的构造函在执行派生类的构造函数时,调用基类的构造函数数时,调用基类的构造函数注意:派生类继承基类的除构造函数和析构注意:派生类继承基类的除构造函数和析构函数以外的所有函数。函数以外的所有函数。15.4.4.1 简单的派生类的构造函数简单的派生类的构造函数简单的派生类简单的派生类:只有一个基类,而且只有一级派生只有一个基类,而且只有一级派生(只有直接派只有直接派生类,没有间接派生类生类,没有间接派生类),在派生类的数据成员中不包含基类的对,在派生类
14、的数据成员中不包含基类的对象象(即子对象即子对象)。简单派生类中我们一般采用在派生类的构造函数初始化列表简单派生类中我们一般采用在派生类的构造函数初始化列表中调用基类的构造函数来对继承基类的数据成员进行初始化。中调用基类的构造函数来对继承基类的数据成员进行初始化。其一般形式为其一般形式为:派生类构造函数名(总参数表列)派生类构造函数名(总参数表列): 基类构造函数名(参数表基类构造函数名(参数表列)列) 派生类中新增数据成员初始化语句派生类中新增数据成员初始化语句、简单派生类的构造函数的形式、简单派生类的构造函数的形式16.#include #includeusing namespace st
15、d;class Student public: Student(int n,string nam,char s) num=n;name=nam;sex=s; Student( ) protected: int num; string name; char sex ; ;17.class Student1: public Student public: Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s) age=a; addr=ad;void show( )coutnum: numendl;coutname: na
16、meendl;coutsex: sexendl;coutage: ageendl;coutaddress: addrendlendl;Student1( ) private: int age; string addr; ;18.在在main函数中,建立对象函数中,建立对象stud1时指定了时指定了5个实参。它们按个实参。它们按顺序传递给派生类构造函数顺序传递给派生类构造函数Student1的形参。然后,派生类构的形参。然后,派生类构造函数将前面造函数将前面3个传递给基类构造函数的形参。个传递给基类构造函数的形参。19.、简单派生类的构造函数的几点说明、简单派生类的构造函数的几点说明a.定义派生
17、类的对象时系统自动调用派生类构造函数之前定义派生类的对象时系统自动调用派生类构造函数之前会先调用其基类的构造函数会先调用其基类的构造函数。基类的构造函数是在派生类的。基类的构造函数是在派生类的构造函数的初始化列表中给出。构造函数的初始化列表中给出。如果在初始化列表中没显式如果在初始化列表中没显式给出调用语句则调用基类的默认构造函数。给出调用语句则调用基类的默认构造函数。 例如前例中派生类的构造函数为:例如前例中派生类的构造函数为:Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)如果在如果在main函数中定义一个
18、函数中定义一个Student1类的对象时。系统会先调用基类的构造函数。然后执行类的对象时。系统会先调用基类的构造函数。然后执行Student1的构造函数体内的代码完成对派生类成员的构造。的构造函数体内的代码完成对派生类成员的构造。如果如果Student1类的构造函数改为类的构造函数改为Student1(int n,string nam,char)则会先调用基类的默认构造函数。则会先调用基类的默认构造函数。20.当派生类构造函数在类外定义时,则只在类外的函数当派生类构造函数在类外定义时,则只在类外的函数定义处加上调用基类的初始化列表。在类内申明的地方不加定义处加上调用基类的初始化列表。在类内申明
19、的地方不加.由前面例题中的构造函数由前面例题中的构造函数Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)可以看出派可以看出派生类的构造函数的初始化列表中是在调用基类的构造函数而生类的构造函数的初始化列表中是在调用基类的构造函数而不是在申明或定义基类的构造函数。所以不是在申明或定义基类的构造函数。所以Student1中的五中的五个参数是形参个参数是形参(带参数类型带参数类型),而其初始化列表中的,而其初始化列表中的Student的三个参数是实参(不带有类型),这些实参取自的三个参数是实参(不带有类型),这些实参取
20、自Student1,所以,所以Studen中的三个参数也可以为常数。例如中的三个参数也可以为常数。例如可将可将Student1的构造函数改为:的构造函数改为: Student1(string nam,char s,int a,string ad):Student(10010,nam,s);.在派生类对象释放时先执行派生类的析构函数,然后在派生类对象释放时先执行派生类的析构函数,然后执行基类的析构函数。执行基类的析构函数。21.例题:定义一个点类例题:定义一个点类Point.由由Point派生出一个圆类派生出一个圆类Circle/Point.h文件文件#ifndef POINT_H#define
21、 POINT_Hclass Pointprotected:float x;float y;public:Point()x=0; y=0;Point(float x,float y);void Show();#endif/Point.cpp文件文件#include #include Point.husing namespace std;Point:Point(float x,float y)this-x=x;this-y=y;void Point:Show()cout(x,y)endl;22./Circle.h文件#ifndef CIRCLE_H#define CIRCLE_H#include
22、#include Point.husing namespace std;class Circle:public Pointprotected:float r;public:Circle(float x,float y,float r);void Show();float GetArea();float GetLength();#endif/Circle.cpp文件#include #include CirCle.husing namespace std;Circle:Circle(float x,float y,float r):Point(x,y)this-r=r;void Circle:S
23、how()cout圆心为:;Point(x,y).Show();cout半径为:rendl;float Circle:GetArea()return 3.14159*r*r;float Circle:GetLength()return 3.14159*2*r;23.4.5.2 有子对象的派生类的构造函数有子对象的派生类的构造函数类的数据成员中还可以包含类对象。例如前面的类的数据成员中还可以包含类对象。例如前面的Student1类类继承自继承自Student类,我们可以在类,我们可以在Student1类中加入一个类中加入一个Student类的对象来表示该同学所在班的班长。如下程序所示:类的对象来
24、表示该同学所在班的班长。如下程序所示:#include #include using namespace std;class Studentpublic: Student(int n, string nam ) num=n;name=nam; void display( ) coutnum:numendlname:nameendl;protected: int num; string name;24.class Student1: public Student public:Student1(int n, string nam,int n1, string nam1,int a, string
25、 ad):Student(n,nam),monitor(n1,nam1) age=a; addr=ad;void show( )cout学生信息为:endl;display(); coutage: ageendl; coutaddress: addrendl; cout班长为:endl;monitor.display( ); private: Student monitor; int age; string addr;25.int main( )Student1 stud1(10010,Wang-li,10001,Li-sun,19,115 Beijing Road,Shanghai);stu
26、d1.show( ); return 0;派生类构造函数的任务应该包括派生类构造函数的任务应该包括3个部分个部分: (1) 对基类数据成员初始化;对基类数据成员初始化;(2) 对子对象数据成员初始化;对子对象数据成员初始化;(3) 对派生类数据成员初始化。对派生类数据成员初始化。其中前两个必须放在派生类的构造函数的初始化列表中其中前两个必须放在派生类的构造函数的初始化列表中进行,第进行,第(3)个可以在函数体中也可以在初始化列表中进行。个可以在函数体中也可以在初始化列表中进行。26.定义派生类构造函数的一般形式为定义派生类构造函数的一般形式为派生类构造函数名(总参数表列)派生类构造函数名(总参
27、数表列): 基类构造函数名(参数基类构造函数名(参数表列),子对象名表列),子对象名(参数表列参数表列) 派生类中新增数成员据成员初始化语句派生类中新增数成员据成员初始化语句执行派生类构造函数的顺序是执行派生类构造函数的顺序是: 调用基类构造函数,对基类数据成员初始化;调用基类构造函数,对基类数据成员初始化; 调用子对象构造函数,对子对象数据成员初始化;调用子对象构造函数,对子对象数据成员初始化; 再执行派生类构造函数本身,对派生类数据成员初始化。再执行派生类构造函数本身,对派生类数据成员初始化。以上次序是固定的,不会因为基类构造函数调用写在前面还是以上次序是固定的,不会因为基类构造函数调用写
28、在前面还是子对象名写在前面而改变。子对象名写在前面而改变。27.例题:定义一个点类例题:定义一个点类Point.由由Point派生出一个圆类派生出一个圆类Circle/Point.h文件文件#ifndef POINT_H#define POINT_Hclass Pointprotected:float x;float y;public:Point()x=0; y=0;Point(float x,float y);void Show();#endif/Point.cpp文件文件#include #include Point.husing namespace std;Point:Point(flo
29、at x,float y)this-x=x;this-y=y;void Point:Show()cout(x,y)endl;4.5.3 多层派生时的构造函数多层派生时的构造函数28./Circle.h文件#ifndef CIRCLE_H#define CIRCLE_H#include #include Point.husing namespace std;class Circle:public Pointprotected:float r;public:Circle(float x,float y,float r);void Show();float GetArea();float GetLe
30、ngth();#endif/Circle.cpp文件#include #include CirCle.husing namespace std;Circle:Circle(float x,float y,float r):Point(x,y)this-r=r;void Circle:Show()cout圆心为:;Point(x,y).Show();cout半径为:rh=h;void Show();float GetArea();float GetVolume();#endif30./Column.cpp#include Column.h#include using namespace std;
31、void Column:Show()Circle:Show();cout高为:hendl;float Column:GetArea()return 2*Circle:GetArea()+GetLength()*h;float Column:GetVolume()return Circle:GetArea()*h;31.在多层派生的情况下:派生类的构造函数初始化列表中只在多层派生的情况下:派生类的构造函数初始化列表中只须写出其上一层派生类的构造函数,不要再写上其间接子类的须写出其上一层派生类的构造函数,不要再写上其间接子类的构造函数。构造函数。32.4.5.4 派生类的析构函数派生类的析构函数在
32、派生时,派生类是不能继承基类的析构函数的,也需要在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。通过派生类的析构函数去调用基类的析构函数。在派生类中可在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。员进行清理工作。基类的清理工作仍然由基类的析构函数负责。在执行派生类的析构函数时,系统会自动调用基类的析构在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。函数和子对象的析构函数,对
33、基类和子对象进行清理。调用的顺序与构造函数正好相反调用的顺序与构造函数正好相反: 先执行派生类自己的析构先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。类进行清理。33.4.6 多重继承多重继承前面讨论的是单继承,即一个类是从一个基类派生而前面讨论的是单继承,即一个类是从一个基类派生而来的。实际上,常常有这样的情况来的。实际上,常常有这样的情况: 一个派生类有两个或多一个派生类有两个或多个基类,
34、派生类从两个或多个基类中继承所需的属性。个基类,派生类从两个或多个基类中继承所需的属性。C+为了适应这种情况,允许一个派生类同时继承多个基类。为了适应这种情况,允许一个派生类同时继承多个基类。这种行为称为这种行为称为多重继承多重继承(multiple inheritance)。4.6.1 声明多重继承的方式声明多重继承的方式声多重继承子类的方法和单继承相似,只是在标明子声多重继承子类的方法和单继承相似,只是在标明子类的位置将继承的父类都写上,且以豆号隔开。类的位置将继承的父类都写上,且以豆号隔开。例如类多重继承了类例如类多重继承了类A,B,C则申明类的方法如下:则申明类的方法如下:class
35、D:public A,protected B,private C类新增加的成员类新增加的成员34.多重继承的子类具有多个父类,子类中具有所有父类多重继承的子类具有多个父类,子类中具有所有父类的所有成员。且对多个父类可以有不同的继承方式,不同的所有成员。且对多个父类可以有不同的继承方式,不同的继承方式决定了继承而来的父类的成员在子类中的访问的继承方式决定了继承而来的父类的成员在子类中的访问属性的不同。属性的不同。4.6.2 多重继承的派生类的构造函数多重继承的派生类的构造函数多重继承派生类的构造函数形式与单继承时的构造多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始列表中
36、包含多个基类构函数形式基本相同,只是在初始列表中包含多个基类构造函数。形式如下:造函数。形式如下:派生类构造函数名派生类构造函数名(总参数表列总参数表列): 基类基类1构造函数构造函数(参参数表列数表列), 基类基类2构造函数构造函数(参数表列参数表列), 基类基类3构造函数构造函数 (参参数表列数表列) 派生类中新增数据成员成员初始化语句派生类中新增数据成员成员初始化语句35.派生类构造函数的执行顺序同样为派生类构造函数的执行顺序同样为: 先调用基类的构造函先调用基类的构造函数,再执行派生类构造函数的函数体。数,再执行派生类构造函数的函数体。调用基类构造函数的调用基类构造函数的顺序是按照声明
37、派生类时基类出现的顺序,与构造函数初始顺序是按照声明派生类时基类出现的顺序,与构造函数初始化列表中基类的排列顺序无关。化列表中基类的排列顺序无关。#include #include using namespace std;class Teacherpublic: Teacher(string nam,int a, string t) name=nam;age=a;title=t;void display( ) coutname:nameendl;coutageageendl;couttitle:titleendl;36.protected: string name; int age; stri
38、ng title; ;class Student public:Student(string nam,char s,float sco)name1=nam;sex=s;score=sco; void display1( ) coutname:name1endl;coutsex:sexendl;coutscore:scoreendl;37.protected: string name1;char sex;float score; ;class Graduate:public Teacher,public Student public:Graduate(string nam,int a,char
39、s, string t,float sco,float w): Teacher(nam,a,t),Student(nam,s,sco),wage(w) void show( ) coutname:nameendl; coutage:ageendl; coutsex:sexendl; coutscore:scoreendl; couttitle:titleendl; coutwages:wageendl; 38.private: float wage; ;int main( )Graduate grad1(Wang-li,24,m,assistant,89.5,1234.5);grad1.sho
40、w( );return 0;在两个基类中分别用在两个基类中分别用name和和name1来代表姓名,其实这是来代表姓名,其实这是同一个人的名字,从同一个人的名字,从Graduate类的构造函数中可以看到总参数类的构造函数中可以看到总参数表中的参数表中的参数nam分别传递给两个基类的构造函数,作为基类构分别传递给两个基类的构造函数,作为基类构造函数的实参。造函数的实参。39.解决这个问题有一个好方法解决这个问题有一个好方法: 在两个基类中可以都使用在两个基类中可以都使用同一个数据成员名同一个数据成员名name,而在,而在show函数中引用数据成员时函数中引用数据成员时指明其作用域,如指明其作用域,
41、如coutname:Teacher:nameendl;这就是惟一的,不致引起二义性,能通过编译,正常运这就是惟一的,不致引起二义性,能通过编译,正常运行。行。通过这个程序还可以发现一个问题通过这个程序还可以发现一个问题: 在多重继承时,从在多重继承时,从不同的基类中会继承一些重复的数据。如果有多个基类,问不同的基类中会继承一些重复的数据。如果有多个基类,问题会更突出。在设计派生类时要细致考虑其数据成员,尽量题会更突出。在设计派生类时要细致考虑其数据成员,尽量减少数据冗余。减少数据冗余。4.6.3 多重继承引起的二义性问题多重继承引起的二义性问题40.多重继承可以反映现实生活中的情况,能够有效地
42、处理多重继承可以反映现实生活中的情况,能够有效地处理一些较复杂的问题,使编写程序具有灵活性,但是多重继承一些较复杂的问题,使编写程序具有灵活性,但是多重继承也引起了一些值得注意的问题,它增加了程序的复杂度,使也引起了一些值得注意的问题,它增加了程序的复杂度,使程序的编写和维护变得相对困难,容易出错。其中最常见的程序的编写和维护变得相对困难,容易出错。其中最常见的问题就是继承的成员同名而产生的二义性问题就是继承的成员同名而产生的二义性(ambiguous)问题。问题。a)多个基类中有同名成员多个基类中有同名成员41.#include #include using namespace std;cl
43、ass Aprotected:int a;public:A(int a)this-a=a;void display( )coutA:a=aa=a;void display( )coutB:a=ab=b;void show()A:display(); B:display();coutC:b=bendl;void main()C c(1,2,3);/c.display(); 二义性;二义性;c.show();c.B:display();43.所以类中数据成员全名应该为下图所示:所以类中数据成员全名应该为下图所示:44.b)多个基类和派生类中都有同名成员多个基类和派生类中都有同名成员class C
44、:public A,public Bprivate: int a;public:C(int Aa,int Ba,int Ca):A(Aa),B(Ba)a=Ca;void display()A:display();B:display();coutC:a=aendl;将前面的类改为如下的形式:将前面的类改为如下的形式:45.这时类的成员构成如左图所示;在这时类的成员构成如左图所示;在类中有三个类中有三个a,三个三个display()函数。函数。思考:思考:如下的如下的main函数能否执行:函数能否执行:void main()C c(1,2,3);c.display();c.A:display();
45、c.B:display();这时这时c.dispaly是可以执行的。原因是类中提供的是可以执行的。原因是类中提供的display函数隐藏了基类和基类中的函数隐藏了基类和基类中的display函数。所函数。所以直接访问以直接访问display函数是在访问类中新增加的成员函数函数是在访问类中新增加的成员函数display();46.C)如果类)如果类A和类和类B是从同一个基类派生的是从同一个基类派生的47.前面提到派生类的对象也是基类的对象,因为派生类中继前面提到派生类的对象也是基类的对象,因为派生类中继承了基类中的所有成员(除构造函数和析构函数)。承了基类中的所有成员(除构造函数和析构函数)。准
46、确的说应该是:准确的说应该是:公有派生类的对象是基类的对象公有派生类的对象是基类的对象。因为。因为只有公有派生类中成员的访问属性与基类完全相同。基类能只有公有派生类中成员的访问属性与基类完全相同。基类能实现的功能在公有派生类中一定能够实现。实现的功能在公有派生类中一定能够实现。4.7 基类与派生类的转换基类与派生类的转换基本数据类型在一定条件下可以进行类型转化,那么基类基本数据类型在一定条件下可以进行类型转化,那么基类对象与派生类对象之间是不是也可以进行转化?对象与派生类对象之间是不是也可以进行转化?由于公有派生类对象也是基类的对象,所以派生类对象可由于公有派生类对象也是基类的对象,所以派生类
47、对象可以自动转化为基类对象。表现在以下几方面:以自动转化为基类对象。表现在以下几方面:48.#include #include using namespace std;class Personpublic:Person(string nam,char s,int a)name=nam;sex=s;age=a;Person()diplay()cout姓名:姓名:nameendl;cout性别:性别:sexendl;cout年龄:年龄:ageendl;protected: string name;char sex;int age;49.class Teacher: public Person pu
48、blic: Teacher(string nam,char s,int a, string t):Person(nam,s,a)title=t; Teacher() diplay()Person:diplay();cout职称:职称:diplay();用派生类对象的地址来初始化基类指针后,只能通过该用派生类对象的地址来初始化基类指针后,只能通过该指针访问派生类中继承的基类的成员。不能访问派生类中指针访问派生类中继承的基类的成员。不能访问派生类中增加的成员。增加的成员。52.() 派生类的对象可以赋值给基类的引用。派生类的对象可以赋值给基类的引用。void main()Teacher t(zha
49、ngSan,m,25,assistant);Person &r=t;/派生类对象初始化基类引用Person p(Lisi,f,26);r=p;t.diplay();用派生类对象来初始化基类引用,引用变量只是派生类用派生类对象来初始化基类引用,引用变量只是派生类对象中基类部分的引用。只能通过引用改变与其继承的派对象中基类部分的引用。只能通过引用改变与其继承的派生类中基类部分的成员。生类中基类部分的成员。以上程序输出:以上程序输出:姓名:姓名:Lisi性别:性别:f年龄:年龄:26职称:职称:assistant53.通过本例可以看到通过本例可以看到: 用指向基类对象的指针变量指向子用指向基类对象的
50、指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但在类对象是合法的、安全的,不会出现编译上的错误。但在应用上却不能完全满足人们的希望,人们有时希望通过使应用上却不能完全满足人们的希望,人们有时希望通过使用基类指针能够调用基类和子类对象的成员。在下一章就用基类指针能够调用基类和子类对象的成员。在下一章就要解决这个问题。办法是使用虚函数和多态性。要解决这个问题。办法是使用虚函数和多态性。4.8 继承与组合继承与组合组合:一个类中有另一个类的对象组合:一个类中有另一个类的对象(子对象子对象)。用组合的方法来设计用组合的方法来设计Point类和类和Circle类,类,54.有了继承,使软件
51、的重用成为可能。继承是有了继承,使软件的重用成为可能。继承是C+和和C的最的最重要的区别之一。重要的区别之一。由于由于C+提供了继承的机制,这就吸引了许多厂商开发各提供了继承的机制,这就吸引了许多厂商开发各类实用的类库。用户将它们作为基类去建立适合于自己的类类实用的类库。用户将它们作为基类去建立适合于自己的类(即派生类即派生类),并在此基础上设计自己的应用程序。类库的出现,并在此基础上设计自己的应用程序。类库的出现使得软件的重用更加方便,现在有一些类库是随着使得软件的重用更加方便,现在有一些类库是随着C+编译编译系统卖给用户的。读者不要认为类库是系统卖给用户的。读者不要认为类库是C+编译系统的
52、一部编译系统的一部分。不同的分。不同的C+编译系统提供的由不同厂商开发的类库一般编译系统提供的由不同厂商开发的类库一般是不同的。是不同的。11.9 继承在软件开发中的重要意义继承在软件开发中的重要意义55.对类库中类的声明一般放在头文件中,类的实现对类库中类的声明一般放在头文件中,类的实现(函数的定函数的定义部分义部分)是单独编译的,以目标代码形式存放在系统某一目录下。是单独编译的,以目标代码形式存放在系统某一目录下。用户使用类库时,不需要了解源代码,但必须知道头文件的使用户使用类库时,不需要了解源代码,但必须知道头文件的使用方法和怎样去连接这些目标代码用方法和怎样去连接这些目标代码(在哪个子
53、目录下在哪个子目录下),以便源程,以便源程序在编译后与之连接。序在编译后与之连接。由于基类是单独编译的,在程序编译时只需对派生类新增由于基类是单独编译的,在程序编译时只需对派生类新增的功能进行编译,这就大大提高了调试程序的效率。如果在必的功能进行编译,这就大大提高了调试程序的效率。如果在必要时修改了基类,只要基类的公用接口不变,派生类不必修改,要时修改了基类,只要基类的公用接口不变,派生类不必修改,但基类需要重新编译,派生类也必须重新编译,否则不起作用。但基类需要重新编译,派生类也必须重新编译,否则不起作用。人们为什么这么看重继承,要求在软件开发中使用继承机人们为什么这么看重继承,要求在软件开
54、发中使用继承机制,尽可能地通过继承建立一批新的类?为什么不是将已有的制,尽可能地通过继承建立一批新的类?为什么不是将已有的类加以修改,使之满足自己应用的要求呢?原因有以下几点:类加以修改,使之满足自己应用的要求呢?原因有以下几点:56.(1) 有许多基类是被程序的其他部分或其他程序使用的,有许多基类是被程序的其他部分或其他程序使用的,这些程序要求保留原有的基类不受破坏。这些程序要求保留原有的基类不受破坏。(2) 用户往往得不到基类的源代码。用户往往得不到基类的源代码。(3) 在类库中,一个基类可能已被指定与用户所需的多种在类库中,一个基类可能已被指定与用户所需的多种组件建立了某种关系,因此在类库中的基类是不容许修改的。组件建立了某种关系,因此在类库中的基类是不容许修改的。(4) 实际上,许多基类并不是从已有的其他程序中选取来实际上,许多基类并不是从已有的其他程序中选取来的,而是专门作为基类设计的。的,而是专门作为基类设计的。(5) 在面向对象程序设计中,需要设计类的层次结构,从在面向对象程序设计中,需要设计类的层次结构,从最初的抽象类出发,每一层派生类的建立都逐步地向着目标最初的抽象类出发,每一层派生类的建立都逐步地向着目标的具体实现前进。的具体实现前进。57.