《《C++及Windows可视化程序设计 刘振安著》第6章.pptx》由会员分享,可在线阅读,更多相关《《C++及Windows可视化程序设计 刘振安著》第6章.pptx(104页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、6.7 虚基类实验习题第1页/共104页从已有的对象类型出发建立一种新的对象类型,使它继承原对象类型的特点和功能,这种思想是面向对象设计方法的主要贡献。继承是对许多问题中分层特性的一种自然描述,因而也是类的具体化和被重新利用的一种手段。通过对已有类进行特殊化(派生)来建立新的数据类型,是一种令人激动的新技巧,这种方法赋予面向对象语言以极大的能力和丰富的表现力。第2页/共104页派生在两个层次上建立了一种类属关系。从概念上讲,类的派生创建了一种软件结构,它真实地反映了实际问题。从软件角度来看,类的派生创建了一种类族。派生类的对象也是基类的一种对象,它可以被用在基类对象所使用的任何地方。可以用多态
2、成员函数仔细调整这种关系,以便使派生类在某些地方与它的基类一致,而在别的地方表现出它自身的行为特征。本章主要讨论C+语言继承方面的语法特征和一般的使用方法。第3页/共104页从一个或多个以前定义的类(基类)产生新类的过程称为派生,这个新类又称为派生类。类的继承是指新类从基类那里得到基类的特征,也就是继承基类的数据和函数。派生的新类同时也可以增加或重新定义数据和操作,这就产生了类的层次性。派生就是创建一个具有别的类的属性和行为的新类的能力。派生和继承的概念也来自于人们认识客观世界的过程。6.1 继承和派生的基本概念第4页/共104页举个简单的例子:“狗”和“黑狗”。当谈论“狗”的时候,知道它是哺
3、乳动物,有4条腿,1条尾巴,喜欢啃肉骨头,现在谈论“黑狗”,人们会怎么说呢?一种是说“黑狗是一种哺乳动物,有4条腿,1条尾巴,喜欢吃肉骨头,并且它的毛是黑色的”。另一种是说:“黑狗就是黑毛狗”。比较一下这两种说法,显然后一种说法更好。那么它好在哪里呢?第一,它更简练;第二,更重要的是它反映了“狗”和“黑狗”这两个概念的内在联系。“狗”和“黑狗”之间存在一条重要的联系,那就是所有的“黑狗”都是“狗”,或者说,“黑狗”是一类特殊的“狗”。根据这一条,“狗”所具有的特征,例如4条腿,1条尾巴等,“黑狗”自然都具有。也就是说,“黑狗”从“狗”那里继承了“狗”的全部特征。第5页/共104页现在我们可以用
4、C+语言来描述这一问题。显然可以定义一个描述“狗”的类dog。但是描述“黑狗”的类blackdog怎么办?为了能准确地描述这两个类之间的关系,C+提供了一种机制,使得人们可以像“黑狗就是黑毛狗”那样定义一个新类blackdog。在这种机制下,类blackdog自动拥有类dog 的所有成员,该类的每一个对象都是类dog 的对象,也就是说,“每一条黑狗都是狗”。这种机制的具体实现留到下一节再说。这一节的主要任务是弄清楚基本概念。现在先来看几个术语。第6页/共104页“黑狗是黑毛的狗”是从一般的dog类通过特殊化而得到类blackdog的。这种通过特殊化已有的类来建立新类的过程,叫做“类的派生”,原
5、有的类叫做“基类”,新建立的类则叫做“派生类”。这里类dog 就是基类,而blackdog是派生类。从类的成员的角度看,派生类自动地将基类的所有成员作为自己的成员,这叫做“继承”。基类和派生类又可以分别叫做“父类”和“子类”,有时也称为“一般类”和“特殊类”。类的派生和继承是面向对象程序设计方法和C+语言最重要的特征之一。第7页/共104页客观世界本身是有层次的,人们认识客观世界的过程中,由一般到特殊的演绎思维发挥着巨大作用。演绎的过程在绝大多数情况下就表现为层次分类的过程。继承使得程序员可以在一个较一般的类的基础上很快地建立一个新类,而不必从零开始设计每个类。继承常用来表示类属关系,不能将继
6、承理解为构成关系。当从现存类中派生出新类时,可以对派生类做如下几种变化:可以增加新的数据成员;可以增加新的成员函数;可以重新定义已有的成员函数;可以改变现有成员的属性。第8页/共104页如图6.1所示,C+中有两种继承:单一继承和多重继承。单一继承,派生类只能有一个基类。多重继承,派生类可以有多个基类。图6.1类的单一继承和多重继承的UML结构图第9页/共104页在图6.1中,箭头指向基类。单一继承形成一个倒挂的树,多重继承形成一个有向无环图。派生类继承了基类所有的数据成员和成员函数,程序员也可以在派生类中添加新的数据成员和成员函数。这样,基类定义了对象的一个集合,派生类通过增添新的成员限制该
7、定义,以便定义这个集合的一个子集。从编码角度讲,派生类从基类中以较低代价换来了大量的灵活性。一旦产生了可靠的基类,只需要调试派生类中所做的修改即可。C+派生类从父类中继承性质时,可使派生类扩展它们,或者对其做些限制,也可改变或删除,甚至不作任何修改。所有这些变化可归结为两类基本的面向对象技术。第一种称为性质约束,即对基类的性质加以限制或删除。第二种称为性质扩展,即增加派生类的性质。第10页/共104页在C+中,声明单一继承的一般形式为:class 派生类名 :访问控制 基类名 private:成员声明列表 protected:成员声明列表 public:成员声明列表;6.2 单一继承 6.2.
8、1 单一继承的一般形式第11页/共104页这里和一般的类的声明一样,用关键字class 声明一个新的类。冒号后面的部分指示这个新类是哪个基类的派生类。“访问控制”是指如何控制基类成员在派生类中的访问属性,它是3个关键字public,protected和private 中的一个。一对大括号“”中是用来声明派生类自己的成员的。这和类的声明一样,不再赘述。第12页/共104页如何初始化派生类的对象呢?当然也应在派生类中声明一个与派生类同名的函数,即它的构造函数。假设从基类Point派生一个描述矩形的类Rectangle。类Rectangle继承Point类的两个数据成员作为矩形的一个顶点,再为Rec
9、tangle类增加两个数据成员H 和W,分别描述它的高和宽。为类Point设计一个构造函数Point(int,int)和显示数据的函数Showxy。为Rectangle类也设计构造函数Rectangle(int,int,int,int)和显示函数Show。派生类增加了两个新的数据成员以及相应的成员函数,同时继承Point的全部成员。【例6.1】是它们的程序实现。6.2.2 派生类的构造函数和析构函数第13页/共104页【例6.1】使用默认内联函数实现单一继承。#includeusing namespace std;class Point private:int X,Y;public:Point
10、(int a,int b)X=a;Y=b;coutPoint.endl;void Showxy()coutX=X,Y=Yendl;Point()coutDelete Pointendl;第14页/共104页class Rectangle:public Point private:int H,W;public:Rectangle(int a,int b,int h,int w):Point(a,b)/构造函数初始化列表H=h;W=w;coutRectangle.endl;void Show()coutH=H,W=Wendl;Rectangle()coutDelete Rectangleendl;
11、void main()Rectangle r1(3,4,5,6);r1.Showxy();/派生类对象调用基类的成员函数第15页/共104页 r1.Show();/派生类对象调用派生类的成员函数程序输出如下:Point./调用基类构造函数Rectangle./调用派生类构造函数X=3,Y=4 /调用基类成员函数Showxy()H=5,W=6 /调用派生类成员函数Show()Delete Rectangle/调用派生类析构函数Delete Point /调用基类析构函数在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。第16页/共10
12、4页定义派生类的构造函数的一般形式为:派生类名 派生类名(参数表0):基类名(参数表)/函数体 冒号后“基类名(参数表)”称为成员初始化列表,参数表给出所调用的基类构造函数所需要的实参。实参的值可以来自“参数表0”,或由表达式给出。可以像Rectangle那样,在类中直接定义为内联函数。下面是在类说明之外定义的示范:RectangleRectangle(int a,int b,int h,int w):Point(a,b)H=h;W=w;coutRectangle.endl;第17页/共104页“参数表0”有4个参数,基类Point的参数表是自己的2个数据成员。当定义派生类的一个对象时,首先调
13、用基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数,如果某个基类仍是一个派生类,则这个过程递归进行。当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。输出结果也证实了这个结论。第18页/共104页如果想修改Rectangle 的Show函数,使得它可以一次显示X、Y、H 和W。怎样修改成员函数Show呢?修改成下面的内容能实现这一目的吗?void Rectangleshow()coutX=X,Y=Y ,H=H,W=W endl;6.2.3 类的保护成员第19页/共104页这段简单程序并不能通过编译。类Rectangle有4个私有成员X、Y、H 和W。这4个私有成员
14、的来源是不一样的。H 和 W是Rectangle 自己定义的,而X 和Y 是从Point那里继承来的。换句话说,X和Y是类Point的私有成员。类的私有成员是只能被它自己的成员函数(不讨论友元)访问的,而Show函数是在类Rectangle 中定义的,它是类Point子类的成员函数,并不是类Point的成员函数,因而不能访问X 和Y。第20页/共104页C+语言规定,公有派生类的成员函数可直接访问基类中定义的或基类(从另一个基类)继承来的公有成员,但不能访问基类的私有成员。这和私有成员的定义是一致的,符合数据封装思想。但这样也有问题,就拿上面的程序来说,在类Rectangle 看来,X、Y、H
15、和W的地位是平等的,现在希望对它们“一视同仁”,但C+语言关于私有成员继承的规定却妨碍这样做。为解决这一矛盾,C+引入了保护成员的概念。第21页/共104页在类声明中,关键字protected之后声明的是类的保护成员。保护成员具有私有成员和公有成员的双重角色:对派生类的成员函数而言,它是公有成员,可以被访问;而对其他函数而言则仍是私有成员,不能被访问。要想在类Rectangle 中使用统一的Show函数,只要把X 和Y 定义成类Point的保护成员就行了。【例6.2】是对【例6.1】修改过的程序。第22页/共104页【例6.2】演示使用protected成员。#include using na
16、mespace std;class Point protected:int X,Y;public:Point(int a,int b)X=a;Y=b;void Show()/基类的Show()函数 coutX=XY=Yendl;class Rectangle:public Point private:int H,W;public:第23页/共104页Rectangle(int,int,int,int);/声明构造函数原型void Show()coutX=X,Y=Y,H=H,W=WShow();/实际调用的是基类的Show函数 Rectangle*pb=&b;/派生类指针pb pb-Show()
17、;/调用派生类的Show函数 a=b;/派生类对象的属性值更新基类对象的属性值 a.Show();X=1,Y=2/用a的Show函数输出对象a的数据成员X=3,Y=4,H=5,W=6/用b的Show函数 /输出对象b的数据成员X=3,Y=4/用b的基类Show函数输出其基类数据成员X=3,Y=4/用b的基类Show函数输出其基类数据成员X=3,Y=4,H=5,W=6/b的指针调用Show函数 /输出对象b的数据成员X=3,Y=4/用a的Show函数输出对象a的数据成员第31页/共104页为什么“p-Show();”不是使用b的Show函数输出“X=3,Y=4,H=5,W=6”?因为“base*
18、pb=&d;”显式地指出指针是基类base的指针,所以编译系统对它采取静态联编,将指针指向基类,调用的是基类的成员函数Show。只有派生类的指针才能调用派生类的成员函数,引用的情况也是如此,程序的输出结构也证实了这一点。对于“a=b;”,从输出结果可以看出,它们的含义就是基类用派生类的属性值代替自己原来的属性值。这就是公有派生的“isa”原则。第32页/共104页2.“isa”和“has-a”的区别类与类之间的关系有两大类。一是继承和派生问题,二是一个类使用另一个类的问题。后者的简单用途是把另一个类的对象作为自己的数据成员或者成员函数的参数。继承首先要掌握公有继承的赋值兼容规则,理解公有继承“
19、就是一个(isa)”的含义。第33页/共104页公有继承的赋值兼容规则如果写成类B公有继承于类A,类B的每一个对象也是一个类A的对象,但反之则不然。如果说对于一个类A的对象是正确的一件事,对于一个类B的对象同样也是真实的,但反之则不然。如果说A表示一个比B更普遍的概念,也就是B表示一个比A更特殊的概念。可以断言:在可以使用类A的对象的任何地方,则类B的对象同样也能使用,因为每一个类B的对象“就是一个”类A的对象。另一方面,如果需要一个类B的对象,则类A的对象就不行;每个B都是A,但反之则不然。C+强制执行公有继承的这种解释。例如:第34页/共104页 class Person.;class S
20、tudent:Public Person.;这两个类所断言的是:每个学生都是人,但并不是每个人都是学生。可以期望任何一件对于人来说是真实的事情,对学生也是真实的,例如他或她都有生日,对于学生同样也是真实的。但却不能期望每一件对于学生来说是真实的事情,对所有的人都是真实的。譬如说他或她就读于一所指定的学校,这对于一般的人就不能都是真实的。“人”的概念要比“学生”的概念来得更广泛些;而“学生”则是一种特殊类型的“人”。第35页/共104页在C+范围内,任何一个要求提供Person类型(或指向Person的指针及引用)参数的函数,也能够使用Student对象(或指向Student的指针及引用)作为参
21、数。例如void dance(const Person&p);/任何人都能跳舞void study(const Student&s);/只有学生要学习Person p;/p是PersonStudent s;/s是Studentdance(p);/对,p是Persondance(s);/对,s是Student,student也是(isa)Personstudy(s);/对study(p);/错!p不都是Student第36页/共104页这只是对公有继承才是正确的。仅当Student类是从Person类中公有地派生出来的时候,C+才是像刚才所描述的那样。公共继承和“isa”的等价性看起来很简单,但
22、在实际应用中并不容易。有时自己的直觉会产生误导。譬如说,企鹅是鸟是一件事,鸟会飞是另一件事。如果已有一个address类,它描述地址这个概念,现在要建立一个worker类,它描述职工这个概念,每个职工都有一个住址,worker类可以有两种定义形式:使用继承或对象成员。如果采用继承,可以按如下形式定义:class worker:public address /;第37页/共104页如果在worker类中定义一个address类的类对象成员,可以按如下形式定义:class worker private address workerAddr;/;使用继承的方法是说职工是一个地址,使用对象的方法声称职
23、工包含有一个地址属性。这两种说法中后者的说法是正确的,即地址只能作为职工的一个属性,在概念上,它们之间没有联系,所以上例中第2种的描述形式是合理的。这就是分层实现。第38页/共104页由此可见,类用于描述一类对象的共同特性,不同种类的对象之间的联系使用继承来表示,对象所具有的属性使用类的成员来表示。分层就是一种处理过程,它通过让分层的类里包含被分层的类的对象作为其数据成员,以便把一个类建立在另一些类之上。例如:class String;/字符串class Address;/某人居住的地方class PhoneNumber;第39页/共104页class Person private:Strin
24、g name;/被分层的对象Address address;/被分层的对象PhoneNumber voiceNumber;/被分层的对象PhoneNumber faxNumber;/被分层的对象 public:;Person类要被分层到String、Address和PhoneNumber这几个类的上面,这是因为它里面含有这些类型的数据成员。第40页/共104页分层也可以叫做包含、嵌入或者聚合。公有继承的意思是“isa”。与此相反,分层的意思是指“has-a(有一个)”或者“is-implemented-in-terms-of(是按实现的)”。上面的Person类表示的是一种“has-a”的关系
25、。一个Person对象有一个名字、一个地址和用于语音及传真通信的两个电话号码。我们不会说一个人就是一个字符串,或者一个人就是一个地址,但会说一个人有一个字符串,并且有一个地址等。许多人做这种分辨时困难不大,搞不清“isa”和“has-a”之间区别的人相对来说也很少见。稍微有些伤脑筋的是“isa”和is-implemented-in-terms-of之间的差异,这可通过例子来加深理解。第41页/共104页3.公有继承存取权限表派生类一般都使用公有继承。使用基类的有基类本身、派生类、对象和外部函数。对派生类而言,使用它的有派生类本身、对象和外部函数。类中可以使用3种成员,它们之间的关系如下表所示。
26、基类基类Point的成员的成员派生方式派生方式Rectangle的成员函数的成员函数对对Point的的访问访问基类对象基类对象a派生类对象派生类对象r1外外 部部 函函 数数void main()private成员成员 public不可访问不可访问不可访问不可访问不可访问不可访问protect成员成员protected不可访问不可访问不可访问不可访问public成员成员 public可访问可访问可访问可访问第42页/共104页最常用的公有继承方式小结保护类型的成员(数据成员和成员函数)介于私有和公有之间。对派生类来讲,它的作用与public成员一样。对类的对象、外部函数以及不属于本类系之外的类
27、来说,它与private成员一样,均是不可访问的,从而保持了类的封装性。“访问控制”决定着基类各成员在派生类中的访问权限。下面将讨论private 和protected继承方式。第43页/共104页4.私有派生通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定义公有的成员函数作为接口。更重要的是,虽然派生类的成员函数可通过自定义的函数访问基类的成员,但将该派生类作为基类再继续派生时,这时即使使用公有派生,原基类公有成员在新的派生类中也将是不可访问的。下面就来看一个私有派生的例子。第44页/共104
28、页【例6.3】私有派生的类继续派生的例子。#includeusing namespace std;class Point private:int X,Y;public:Point(int a,int b)X=a;Y=b;void Show()coutX=X,Y=Yendl;第45页/共104页class Rectangle:private Point private:int H,W;public:Rectangle(int a,int b,int h,int w):Point(a,b)H=h;W=w;void Show()PointShow();coutH=HW=Wendl;class Tes
29、t:public Rectangle public:Test(int a,int b,int h,int w):Rectangle(a,b,h,w)void Show()RectangleShow();第46页/共104页在这个例子中,基类的公有成员函数Show通过私有派生成了派生类的私有成员函数,Test虽然是公有派生,但它已经无法使用基类的Show函数,即不能通过“PointShow();”方式使用Point类的Show函数,这就彻底切断了基类与外界的联系。私有派生的这一特点不利于进一步派生,因而实际中私有派生用得并不多。第47页/共104页5.保护派生派生也可以使用protected。这
30、种派生使原来的权限都降一级使用:private变为不可访问;protected变为private;public变为protected。因为限制了数据成员和成员函数的访问权限,所以用得较少。它与private继承的区别主要在下一级的派生中。如果将上例Rectangle改为保护继承方式,则在Test类中可以使用基类的Show函数,则下面函数的定义是正确的:第48页/共104页class Test:public Rectangle public:Test(int a,int b,int h,int w):Rectangle(a,b,h,w)void Show()PointShow();/可以使用基类
31、Point的成员 RectangleShow();第49页/共104页1.类模板继承的方法同样,类模板也可以继承,继承的方法也一样。声明模板继承之前,必须重新声明模板。6.3 继承类模板第50页/共104页【例6.4】演示从一个普通的基类Base派生出一个类模板的方法。#include using namespace std;class Base /普通的基类Base public:Base(int a)x=a;int Getx()return x;void Showb()coutxendl;private:int x;第51页/共104页template /派生出一个类模板Derivedcl
32、ass Derived:public Base public:Derived(T a,int b):Base(b)y=a;T Gety()return y;void Showd()couty Getx()endl;private:T y;第52页/共104页类模板Derived继承了基类Base的性质,并可以使用构造函数将参数传给基类。注意:在测试程序里不能改变基类的数据类型。下面是测试程序,输出结果如右边红框内所示:void main()Base A(458);A.Showb();DerivedD1(5678,1234);DerivedD2(5.7,1234);DerivedD3(It is
33、,1234);DerivedD4(=,1234);D1.Showd();D2.Showd();D3.Showd();D4.Showd();输出结果如下:458567812345.71234Itis1234=1234第53页/共104页【例6.5】演示类模板继承的实例。这个程序简单地演示了从一个模板基类Base派生出一个类模板的方法。#include using namespace std;templateclass Base public:void Showb(T);template/声明两个数据类型class Derived:public Base/T2类型供基类使用 public:void
34、 Showd(T1,T2);第54页/共104页template/不要忘记重写此行void BaseShowb(T b)/不要忘记coutbendl;template/不要忘记重写此行void DerivedShowd(T1 obj1,T2 obj2)coutobj1 obj2endl;定义Showd时,不要忘记参数,应写作:Showd(T1 obj1,T2 obj2)下面用一个主函数来演示如何使用它们。第55页/共104页void main()Derivedobj1;obj1.Showb(55.58);/使用基类obj1.Showd(The value is:,53.25);Derivedo
35、bj2;/两个数据类型可以一样obj2.Showd(32,89);Derivedobj3;obj3.Showd(5.8,is ok.);Derivedobj4;obj4.Showd(I,and you);obj4.Showb(I and you);/使用基类第56页/共104页输出结果如下:55.58 The value is:53.25 32 89 5.8 is ok.I and you I and you派生类实例化时,又将一个形式参数传给基类,从而完成两个类的初始化过程。与普通类的性质一样,这里也可单独使用基类。【例6.6】是使用类模板改写【例6.2】的程序。成员函数使用内联函数,构造函
36、数在类外面定义。虽然它们有几个数据成员,但程序要求它们的类型一样,所以只有一个类型参数。第57页/共104页【例6.6】演示类模板构造函数。#include using namespace std;template class Point protected:T X,Y;public:Point(T,T);void Show()coutX=X,Y=Yendl;Point()coutDelete Point X Yendl;第58页/共104页templateclass Rectangle:public Point private:T H,W;public:Rectangle(T,T,T,T);
37、void Show()coutX=X,Y=Y ,H=H,W=Wendl;Rectangle()coutDelete Rectangle endl;templatePointPoint(T a,T b)X=a;Y=b;第59页/共104页templateRectangleRectangle(T a,T b,T h,T w):Point(a,b)H=h;W=w;void main()Pointp1(I am,a point.);p1.Show();Rectangler1(3,4,5,6);r1.Show();Rectangler(3.3,4.4,5.5,6.6);r.Show();第60页/共10
38、4页程序分别演示使用char*,int,double构造对象。输出结果如下:X=I am,Y=a point.X=3,Y=4,H=5,W=6 X=3.3,Y=4.4,H=5.5,W=6.6 Delete Rectangle Delete Point 3.3 4.4 Delete Rectangle Delete Point 3 4 Delete Point I am a point.第61页/共104页2.模板与继承的关系用模板和继承的方法都可以派生出一个类系,但是它们之间却有着本质的不同。从处理的数据和数据的处理方式来看,模板反映的是不同类型的相同操作。定义好的类模板,经过不同的实例化之后,
39、就可处理不同的数据。但是不管这些数据类型如何变化,它们的操作集是一致的,不可能增加或者改变。以堆栈类模板为例,经过实例化之后,它可能是整型堆栈,也可能是字符型堆栈,但不论是哪一种堆栈,它所提供的操作总是那几种。而且,相同的类模板的不同实例一般没有任何联系。第62页/共104页与之不同的是,继承所处理的数据没有限制,可能是同一类型的,也可能是不同类型的,很多情况下同一类系的各种类处理的都是同一类型。数据的操作在继承中大多发生了变化,派生类可能提供更多的操作,也可能改变原有的操作,使得数据的处理更简单和快捷。各种类之间有着兄弟、父子等种种联系。从类系的成员看,继承类系的成员较为稳定,一般来说,它们
40、不会因为其他类型体系的改变而增加新的成员。而模板类系由于其特点,对于新生的类型都可能产生不同的实例,也就是说,每增加一种新的类型,模板类系就可能增加很多的成员。以堆栈类来说,如果新定义一个类型New_type,就可以派生一个它的实例Stack,可见模板类系的成员是不断变化的。第63页/共104页从类系的动态性能角度来看,类模板具有更多的动态特征。一旦完成一个继承类系的设计,它能处理的数据和处理数据的方法就确定了,除非新继承一个类来完成新的功能。类模板在设计完成之后,可以处理的数据类型却可以不断增加,并且类模板的实例可在编译时刻初始化,从而具有更好的灵活性。综上所述,模板和继承在不同的方面具有各
41、自的优点,应该根据实际情况加以选择,以方便编程和使用。如果一个类系的不同类之间有着相依的关系,应该更多地考虑继承。如果希望提供的操作方法对于不同的类型是一样的,则模板将是更好的选择。在处理实际问题时,希望读者能够找到恰当的方法来设计自己所需要的类系。第64页/共104页一个类从多个基类派生的一般形式是:class 类名1:访问控制 类名2,访问控制 类名3,.,访问控制 类名n /定义派生类自己的成员;类名1继承了类名2到类名n的所有数据成员和成员函数,访问控制用于限制其后的类中的成员在类名1中的访问权限,其规则和单一继承情况一样。多重继承可以视为是单一继承的扩展。6.4 多重继承第65页/共
42、104页【例6.7】演示多重继承的例子。#include using namespace std;class A private:int a;public:void setA(int x)a=x;void showA()cout a=aendl;class B private:int b;第66页/共104页 public:void setB(int x)b=x;void showB()coutb=bendl;class C:public A,private B private:int c;public:void setC(int x,int y)c=x;setB(y);void showC(
43、)showB();coutc=cendl;第67页/共104页类C从类A公有派生,因此,类A的公有成员(保护成员)在类C中仍是公有的(保护的)。类C从类B私有派生,类B的所有成员在类C中是私有的。这些成员在派生类中的可访问性和单一继承中讨论的一样。由于类B被私有继承,因此,类C应该负责维护类B的数据成员值和显示,所以在showC和setC中分别调用类B的成员函数showB和setB。第68页/共104页下面是使用这个类的主程序:void main()C obj;obj.setA(53);obj.showA();/输出a=53obj.setC(55,58);obj.showC();/输出b=58
44、c=55注意:使用obj.setB(5)和obj.showB()都是错误的。第69页/共104页在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。假设类Y从类X1,X2,.,Xn派生,则定义派生类的构造函数的一般形式为:Y Y(参数表0):X1(参数表1),X2(参数表2),.,Xn(参数表n)/定义6.5 继承的构造函数与析构函数调用顺序第70页/共104页成员初始化列表中各项用逗号隔开,每项由基类名以及括号内的参数表组成,参数表给出所调用的构造函数所需要的实参。实参的值可以来自“参数表0”,或由表达式给出。如果某个项的参数表为空
45、,则该项可以从成员初始化列表中省去。当说明Y类的一个对象时,首先调用基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数,如果某个基类仍是一个派生类,则这个过程递归进行。当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。第71页/共104页【例6.8】演示构造函数和析构函数调用顺序。#include using namespace std;class base private:int a;public:base(int i)a=i;coutconstructing base a=aendl;base()coutdestroying base a=aendl;第72页/
46、共104页class derived :public base private:int d;public:derived(int i,int j);derived()coutdestroying derived d=dendl;derived derived(int i,int j):base(i)d=j;coutconstructing derived d=d endl;void main()derived d(5,8);第73页/共104页程序输出结果如下:constructing base a=5 constructing derived d=8 destroying derived d
47、=8 destroying base a=5如果派生类还包括对象成员,则对对象成员的构造函数的调用,仍然在初始化列表中进行。如果将上例的类derived修改如下:第74页/共104页class derived:public base private:int d;base number;public:derived(int i,int j,int k);derived()coutdestroying derived d=dendl;derived derived(int i,int j,int k):base(i),number(j)d=k;coutconstructing derived d=
48、d endl;void main()derived d(5,8,9);第75页/共104页则输出结果为:constructing base a=5 constructing base a=8 constructing derived d=9 destroying derived d=9 destroying base a=8 destroying base a=5当建立derived类的对象时,基类的构造函数被先调用,对象成员的构造函数次之,最后执行派生类的构造函数。在有多个对象成员的情况下,这些对象成员的调用顺序取决于它们在派生类中被说明的顺序。第76页/共104页【例6.9】多重继承的例子
49、。#include using namespace std;class base1 public:base1()coutconstructing base1endl;base1()coutdestorying base1endl;class base2 public:base2()coutconstructing base2endl;base2()coutdestorying base2endl;第77页/共104页class base3 public:base3()coutconstructing base3endl;base3()coutdestorying base3endl;class
50、 base4 public:base4()coutconstructing base4endl;base4()coutdestorying base4endl;class derived:public base2,public base1,public base3 private:base4 memobj;第78页/共104页void main()derived d;执行这个程序产生下列结果:constructing base2constructing base1constructing base3constructing base4destroying base4destroying bas