《面向对象程序设计 ch7.ppt》由会员分享,可在线阅读,更多相关《面向对象程序设计 ch7.ppt(75页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第7 7章章 继承和派生继承和派生 从已有的对象类型出发建立一种新的对象类型,使它从已有的对象类型出发建立一种新的对象类型,使它继承原对象类型的特点和功能,这种思想是面向对象设计继承原对象类型的特点和功能,这种思想是面向对象设计方法的主要贡献。方法的主要贡献。通过对已有类进行特殊化通过对已有类进行特殊化(派生派生)来建立新的数据类型,来建立新的数据类型,就使得面向对象语言具有极大的能力和丰富的表现力。从就使得面向对象语言具有极大的能力和丰富的表现力。从概念上讲,类的派生创建了一种软件结构,它真实地反映概念上讲,类的派生创建了一种软件结构,它真实地反映了实际问题。从软件角度来看,类的派生创建了
2、一种类族。了实际问题。从软件角度来看,类的派生创建了一种类族。派生类的对象也是基类的一种对象,它可以被用在基类对派生类的对象也是基类的一种对象,它可以被用在基类对象所使用的任何地方。可以用多态成员函数仔细调整这种象所使用的任何地方。可以用多态成员函数仔细调整这种关系,以便使派生类在某些地方与它的基类一致,而在别关系,以便使派生类在某些地方与它的基类一致,而在别的地方表现出它自身的行为特征。的地方表现出它自身的行为特征。本章主要讨论本章主要讨论C+C+语言继承方面的语法特征和一般的使语言继承方面的语法特征和一般的使用方法。用方法。主要内容7.1继承和派生的基本概念继承和派生的基本概念7.2单一继
3、承单一继承7.3多重继承多重继承7.4二义性及其支配规则二义性及其支配规则7.5设计实例设计实例7.1 继承和派生的基本概念继承和派生的基本概念 派生和继承的概念也来自于人们认识客观世界的过程。派生和继承的概念也来自于人们认识客观世界的过程。举个简单的例子:举个简单的例子:“狗狗”和和“黑狗黑狗”。当谈论。当谈论“狗狗”的时的时候,知道它是哺乳动物,有候,知道它是哺乳动物,有4 4条腿,条腿,1 1条尾巴,喜欢啃肉骨条尾巴,喜欢啃肉骨头,头,。现在谈论。现在谈论“黑狗黑狗”,人们会怎么说呢?当然可,人们会怎么说呢?当然可以说:以说:“黑狗是一种哺乳动物,有黑狗是一种哺乳动物,有4 4条腿,条腿
4、,1 1条尾巴,喜欢条尾巴,喜欢吃肉骨头,吃肉骨头,并且它的毛是黑色的,并且它的毛是黑色的”。但是人们一般。但是人们一般都不这么说,而是说:都不这么说,而是说:“黑狗就是黑毛的狗黑狗就是黑毛的狗”。比较一下。比较一下这两种说法,显然后一种说法更好。那么它好在哪里呢?这两种说法,显然后一种说法更好。那么它好在哪里呢?第一,它更简炼;第二,更重要的是它反映了第一,它更简炼;第二,更重要的是它反映了“狗狗”和和“黑狗黑狗”这两个概念的内在联系。这两个概念的内在联系。“狗狗”和和“黑狗黑狗”之间存之间存在一条重要的联系,那就是所有的在一条重要的联系,那就是所有的“黑狗黑狗”都是都是“狗狗”,或者说,或
5、者说,“黑狗黑狗”是一类特殊的是一类特殊的“狗狗”。根据这一条,。根据这一条,“狗狗”所具有的特征,例如所具有的特征,例如4 4条腿,条腿,1 1条尾巴等,条尾巴等,“黑狗黑狗”自自然都具有。也就是说,然都具有。也就是说,“黑狗黑狗”从从“狗狗”那里继承了那里继承了“狗狗”的全部特征。的全部特征。现在我们用现在我们用C+C+语言来描述这一问题。显然可以定义一个语言来描述这一问题。显然可以定义一个描述描述“狗狗”的类的类dog dog。但是描述但是描述“黑狗黑狗”的类的类blackdogblackdog怎怎么办?为了能准确地描述这两个类之间的关系,么办?为了能准确地描述这两个类之间的关系,C+C
6、+提供提供了一种机制,使得人们可以像了一种机制,使得人们可以像“黑狗就是黑毛的狗黑狗就是黑毛的狗”那样那样定义一个新类定义一个新类blackdogblackdog。在这种机制下,类在这种机制下,类blackdogblackdog自动自动拥有类拥有类dog dog 的所有成员,该类的每一个对象都是类的所有成员,该类的每一个对象都是类dog dog 的的对象,也就是说,对象,也就是说,“每一条黑狗都是狗每一条黑狗都是狗”。这种机制的具体实现留到下一节再说。这一节的主要任务这种机制的具体实现留到下一节再说。这一节的主要任务是弄清楚基本概念。现在先来看几个术语。是弄清楚基本概念。现在先来看几个术语。首
7、先,首先,“黑狗是黑毛的狗黑狗是黑毛的狗”是从一般的是从一般的dogdog类通过特殊化类通过特殊化而得到类而得到类blackdogblackdog的。这种通过特殊化已有的类来建立新的。这种通过特殊化已有的类来建立新类的过程,叫做类的过程,叫做“类的派生类的派生”,原有的类叫做,原有的类叫做“基类基类”,新建立的类则叫做新建立的类则叫做“派生类派生类”。这里类。这里类dog dog 就是基类,而就是基类,而blackdogblackdog是派生类。另一方面,从类的成员的角度看,派是派生类。另一方面,从类的成员的角度看,派生类自动地将基类的所有成员作为自己的成员,这叫做生类自动地将基类的所有成员作
8、为自己的成员,这叫做“继承继承”。基类和派生类又可以分别叫做。基类和派生类又可以分别叫做“父类父类”和和“子类子类”,有时也称为,有时也称为“一般类一般类”和和“特殊类特殊类”。类的派生和继承是面向对象程序设计方法和类的派生和继承是面向对象程序设计方法和C+C+语言最重语言最重要的特征之一。首先,客观世界本身是有层次的,人们认要的特征之一。首先,客观世界本身是有层次的,人们认识客观世界的过程中,由一般到特殊的演绎思维发挥着巨识客观世界的过程中,由一般到特殊的演绎思维发挥着巨大作用。演绎的过程在绝大多数情况下就表现为层次分类大作用。演绎的过程在绝大多数情况下就表现为层次分类的过程。继承使得程序员
9、可以在一个较一般的类的基础上的过程。继承使得程序员可以在一个较一般的类的基础上很快地建立一个新类,而不必从零开始设计每个类。很快地建立一个新类,而不必从零开始设计每个类。从一个或多个以前定义的类从一个或多个以前定义的类(基类基类)产生新类的过程称为产生新类的过程称为派生,这个新类称为派生类。派生的新类同时也可以增加派生,这个新类称为派生类。派生的新类同时也可以增加或重新定义数据和操作,这就产生了类的层次性。或重新定义数据和操作,这就产生了类的层次性。类的继承是指新类继承基类的成员。继承常用来表示类属类的继承是指新类继承基类的成员。继承常用来表示类属关系,不能将继承理解为构成关系。当从现存类中派
10、生出关系,不能将继承理解为构成关系。当从现存类中派生出新类时,可以对派生类做如下几种变化:新类时,可以对派生类做如下几种变化:可以增加新的数据成员;可以增加新的数据成员;可以增加新的成员函数;可以增加新的成员函数;可以重新定义已有的成员函数;可以重新定义已有的成员函数;可以改变现有成员的属性。可以改变现有成员的属性。如图如图7.17.1所示,所示,C+C+中有两种继承:单一继承和多重继承。中有两种继承:单一继承和多重继承。对于单一继承,派生类只能有一个基类;对于多重继承,对于单一继承,派生类只能有一个基类;对于多重继承,派生类可以有多个基类。派生类可以有多个基类。图图7.1类的单一继承和多重继
11、承的类的单一继承和多重继承的UML结构图结构图在图在图7.17.1中,箭头指向基类。单一继承形成一个倒挂的树。中,箭头指向基类。单一继承形成一个倒挂的树。派生类继承了基类所有的数据成员和成员函数,程序员派生类继承了基类所有的数据成员和成员函数,程序员也可以在派生类中添加新的数据成员和成员函数。也可以在派生类中添加新的数据成员和成员函数。这样,基类定义了对象的一个集合,派生类通过增添新这样,基类定义了对象的一个集合,派生类通过增添新的成员限制基类的定义,以便定义基类对象集合的一个的成员限制基类的定义,以便定义基类对象集合的一个子集。子集。由此可见,从编码角度讲,派生类从基类中以较低代价由此可见,
12、从编码角度讲,派生类从基类中以较低代价换来了大量的灵活性。一旦产生了可靠的基类,只需要换来了大量的灵活性。一旦产生了可靠的基类,只需要调试派生类中所做的修改即可。调试派生类中所做的修改即可。C+C+派生类从父类中继承性质时,可使派生类扩展它们,派生类从父类中继承性质时,可使派生类扩展它们,或者对其做些限制,也可改变或删除,甚至不作任何修或者对其做些限制,也可改变或删除,甚至不作任何修改。所有这些变化可归结为两类基本的面向对象技术。改。所有这些变化可归结为两类基本的面向对象技术。第一种称为性质约束,即对基类的性质加以限制或删除。第一种称为性质约束,即对基类的性质加以限制或删除。第二种称为性质扩展
13、,即增加派生类的性质。第二种称为性质扩展,即增加派生类的性质。7.2单一继承单一继承7.2.1单一继承的一般形式单一继承的一般形式在在C+中,声明单一继承的一般形式为:中,声明单一继承的一般形式为:class派生类名:访问控制派生类名:访问控制基类名基类名private:成员声明列表成员声明列表protected:成员声明列表成员声明列表public:成员声明列表成员声明列表;这里和一般的类的声明一样,用关键字这里和一般的类的声明一样,用关键字class class 声明声明一个新的类。一个新的类。冒号后面的部分指示这个新类是哪个基类的派生类。冒号后面的部分指示这个新类是哪个基类的派生类。所谓
14、所谓“访问控制访问控制”是指如何控制基类成员在派生类是指如何控制基类成员在派生类中的访问属性,它是中的访问属性,它是3 3个关键字个关键字public,protected和和private中的一个。中的一个。一对大括号一对大括号“”中是用来声明派生中是用来声明派生类自己的成类自己的成员的。这和类的声明一样,不再赘述。员的。这和类的声明一样,不再赘述。7.2.2派生类的构造函数和析构函数派生类的构造函数和析构函数如何初始化派生类的对象呢?当然也应在派生类中声明一如何初始化派生类的对象呢?当然也应在派生类中声明一个与派生类同名的函数。假设从基类个与派生类同名的函数。假设从基类Point派生一个描述
15、派生一个描述矩形的类矩形的类Rectangle。类类Rectangle继承继承Point类的两个数据类的两个数据成员作为矩形的一个顶点,再为成员作为矩形的一个顶点,再为Rectangle类增加两个数类增加两个数据成员据成员H和和W,分别描述它的高和宽。为类分别描述它的高和宽。为类Point设计一设计一个构造函数个构造函数Point(int,int)和显示数据的函数和显示数据的函数Showxy。为为Rectangle类也设计构造函数类也设计构造函数Rectangle(int,int,int,int)和显和显示函数示函数Show。由此可见,派生类增加了两个新的数据成由此可见,派生类增加了两个新的数
16、据成员以及相应的成员函数,同时继承员以及相应的成员函数,同时继承Point的全部成员。的全部成员。【例例7.1】是它们的程序实现。是它们的程序实现。【例【例7.1】使用默认内联函数实现单一继承。使用默认内联函数实现单一继承。#includeusingnamespacestd;classPointprivate:intx,y;public:Point(inta,intb)x=a;y=b;coutPoint.endl;voidShowxy()coutx=x,y=yendl;Point()coutDeletePointendl;classRectangle:publicPointprivate:in
17、tH,W;public:Rectangle(inta,intb,inth,intw):Point(a,b)/构造函构造函/数初始化列表数初始化列表H=h;W=w;coutRectangle.endl;voidShow()coutH=H,W=Wendl;Rectangle()coutDeleteRectangleendl;voidmain()Rectangler1(3,4,5,6);r1.Showxy();/派生类对象调用基类的成员函数派生类对象调用基类的成员函数r1.Show();/派生类对象调用派生类的成员函数派生类对象调用派生类的成员函数程序输出如下:程序输出如下:Point./调用基类构
18、造函数调用基类构造函数Rectangle./调用派生类构造函数调用派生类构造函数x=3,y=4/调用基类成员函数调用基类成员函数Showxy()H=5,W=6/调用派生类成员函数调用派生类成员函数Show()DeleteRectangle/调用派生类析构函数调用派生类析构函数DeletePoint/调用基类析构函数调用基类析构函数在派生类中继承的基类成员的初始化,需要由派生类的构在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。有类似之处。定义派生类的构造函数的一般形式为:定义派生类
19、的构造函数的一般形式为:派生类名派生类名 :派生类名派生类名(参数表参数表0):0):基类名基类名(参数表参数表)././函数体函数体 冒号后冒号后“基类名基类名(参数表参数表)”称为成员初始化列表,参数称为成员初始化列表,参数表给出所调用的基类构造函数所需要的实参。实参的值可表给出所调用的基类构造函数所需要的实参。实参的值可以来自以来自“参数表参数表0 0”,或由表达式给出。可以像,或由表达式给出。可以像RectangleRectangle那样,在类中直接定义为内联函数。下面是在类说明之外那样,在类中直接定义为内联函数。下面是在类说明之外定义的示范:定义的示范:Rectangle:Recta
20、ngle(inta,intb,inth,intw):Point(a,b)H=h;W=w;coutRectangle.endl;“参数表参数表0 0”有有4 4个参数,基类个参数,基类PointPoint的参数表是自己的的参数表是自己的2 2个数据成员。个数据成员。构造函数(包括析构函数)是不被继承的,所以一个派生构造函数(包括析构函数)是不被继承的,所以一个派生类只能调用它的直接基类的构造函数。当定义派生类的一类只能调用它的直接基类的构造函数。当定义派生类的一个对象时,首先调用基类的构造函数,对基类成员进行初个对象时,首先调用基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数,如果
21、某个基类仍是一始化,然后执行派生类的构造函数,如果某个基类仍是一个派生类,则这个过程递归进行。当该对象消失时,析构个派生类,则这个过程递归进行。当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。输出函数的执行顺序和执行构造函数时的顺序正好相反。输出结果也证实了这个结论。结果也证实了这个结论。7.2.3 类的保护成员类的保护成员现在修改现在修改Rectangle的的Show函数,使得它可以一次显示函数,使得它可以一次显示x、y、H和和W。怎样修改成员函数怎样修改成员函数Show呢?修改成下面呢?修改成下面的内容能实现这一目的吗?的内容能实现这一目的吗?voidRectangle:
22、show()coutx=x,y=y,H=H,W=Wendl;这段简单程序并不能通过编译。类这段简单程序并不能通过编译。类Rectangle有有4个私有成个私有成员员x、y、H和和W。这这4个私有成员的来源是不一样的。个私有成员的来源是不一样的。H和和W是是Rectangle自己定义的,而自己定义的,而x和和y是从是从Point那里继那里继承来的。承来的。换句话说,换句话说,x和和y是类是类Point的私有成员。类的私有成员是的私有成员。类的私有成员是只能被它自己的成员函数只能被它自己的成员函数(不讨论友元不讨论友元)访问的,而访问的,而Show函数是在类函数是在类Rectangle中定义的,它
23、是类中定义的,它是类Point子类的成员子类的成员函数,并不是类函数,并不是类Point的成员函数,因而不能访问的成员函数,因而不能访问x和和y。C+语言规定,公有派生类的成员函数可直接访问基类中语言规定,公有派生类的成员函数可直接访问基类中定义的或基类(从另一个基类)继承来的公有成员,但不定义的或基类(从另一个基类)继承来的公有成员,但不能访问基类的私有成员。这和私有成员的定义是一致的,能访问基类的私有成员。这和私有成员的定义是一致的,符合数据封装思想。但这样也有问题,就拿上面的程序来符合数据封装思想。但这样也有问题,就拿上面的程序来说,在类说,在类Rectangle看来,看来,x、y、H和
24、和W的地位是平等的地位是平等的,现在希望对它们的,现在希望对它们“一视同仁一视同仁”,但,但C+语言关于私有语言关于私有成员继承的规定却妨碍这样做。为解决这一矛盾,成员继承的规定却妨碍这样做。为解决这一矛盾,C+引引入了保护成员的概念。入了保护成员的概念。在类声明中,关键字在类声明中,关键字protected之后声明的是类的保护成员。之后声明的是类的保护成员。保护成员具有私有成员和公有成员的双重角色:对派生类保护成员具有私有成员和公有成员的双重角色:对派生类的成员函数而言,它是公有成员,可以被访问;而对其他的成员函数而言,它是公有成员,可以被访问;而对其他函数而言则仍是私有成员,不能被访问。因
25、此,要想在类函数而言则仍是私有成员,不能被访问。因此,要想在类Rectangle中使用统一的中使用统一的Show函数,只要把函数,只要把x和和y定义成定义成类类Point的保护成员就行了。的保护成员就行了。【例【例7.2】是修改过的程序。是修改过的程序。【例【例7.2】演示使用演示使用protected成员。成员。#includeusingnamespacestd;classPointprotected:intx,y;public:Point(inta,intb)x=a;y=b;voidShow()cout“x=”x“,y=”yendl;/基基类类/的的Show()函数函数;classRect
26、angle:publicPointprivate:intH,W;public:Rectangle(int,int,int,int);/构造函数原型构造函数原型voidShow()coutx=x,y=y,H=H,W=WShow();/实际调用的是基类的实际调用的是基类的Show函数函数Rectangle*pb=&b;/派生类指针派生类指针pbpb-Show();/调用派生类的调用派生类的Show函数函数a=b;/派生类对象的属性值更新基类对象的属性值派生类对象的属性值更新基类对象的属性值a.Show();程序输出如下:程序输出如下:x=1,y=2/用用a的的Show函数输出对象函数输出对象a的数
27、据成员的数据成员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的数据成员的数据成员以指针为例,为什么以指针为例,为什么“p-Show();”不是使用不是使用b的的Show函函数输出数输出“x=3,y=4,H
28、=5,W=6”?因为因为“base*pb=&d;”显显式地指出指针是基类式地指出指针是基类base的指针,所以编译系统对它采取的指针,所以编译系统对它采取静态联编,将指针指向基类,调用的是基类的成员函数静态联编,将指针指向基类,调用的是基类的成员函数Show。只有派生类的指针才能调用派生类的成员函数,只有派生类的指针才能调用派生类的成员函数,引用的情况也是如此,程序的输出结构也证实了这一点。引用的情况也是如此,程序的输出结构也证实了这一点。对于对于“a=b;”,从输出结果可以看出,它们的含义就是基从输出结果可以看出,它们的含义就是基类用派生类的属性值代替自己原来的属性值。这就是公有类用派生类的
29、属性值代替自己原来的属性值。这就是公有派生的派生的“isa”原则。原则。2.“isa”和和“has-a”的区别的区别类与类之间的关系有两大类。一是继承和派生问题,二是类与类之间的关系有两大类。一是继承和派生问题,二是一个类使用另一个类的问题。后者的简单用途是把另一个一个类使用另一个类的问题。后者的简单用途是把另一个类的对象作为自己的数据成员或者成员函数的参数。类的对象作为自己的数据成员或者成员函数的参数。继承首先要掌握公有继承的赋值兼容规则,理解公有继承继承首先要掌握公有继承的赋值兼容规则,理解公有继承“就是一个(就是一个(isa)”的含义。如果写成类的含义。如果写成类B公有继承于类公有继承于
30、类A,在可以使用类在可以使用类A的对象的任何地方,则类的对象的任何地方,则类B的对象同样的对象同样也能使用,因为每一个类也能使用,因为每一个类B的对象的对象“就是一个就是一个”类类A的对的对象。另一方面,如果需要一个类象。另一方面,如果需要一个类B的对象,则类的对象,则类A的对象的对象就不行:每个就不行:每个B都是都是A,但反之则不然。但反之则不然。C+强制执行公强制执行公有继承的这种规则。例如:有继承的这种规则。例如:classPerson.;classStudent:PublicPerson.;这两个类所断言的是:每个学生都是人,但并不是每个人这两个类所断言的是:每个学生都是人,但并不是每
31、个人都是学生。可以期望任何一件对于人来说是真实的事情,都是学生。可以期望任何一件对于人来说是真实的事情,对学生也是真实的,例如他或她都有生日,对于学生同样对学生也是真实的,例如他或她都有生日,对于学生同样也是真实的。但却不能期望每一件对于学生来说是真实的也是真实的。但却不能期望每一件对于学生来说是真实的事情,对所有的人都是真实的。譬如说他或她就读于一所事情,对所有的人都是真实的。譬如说他或她就读于一所指定的学校,这对于一般的人就不能都是真实的。指定的学校,这对于一般的人就不能都是真实的。“人人”的概念要比的概念要比“学生学生”的概念来得更广泛些;而的概念来得更广泛些;而“学生学生”则则是一种特
32、殊类型的是一种特殊类型的“人人”。在在C+范围内,任何一个要求提供范围内,任何一个要求提供Person类型(或指向类型(或指向Person的指针及引用)参数的函数,也能够使用的指针及引用)参数的函数,也能够使用Student对象(或指向对象(或指向Student的指针及引用)作为参数。例如:的指针及引用)作为参数。例如:voiddance(constPerson&p);/任何人都能跳舞任何人都能跳舞voidstudy(constStudent&s);/只有学生要学习只有学生要学习Personp;/p是是PersonStudents;/s是是Studentdance(p);/对,对,p是是Per
33、sondance(s);/对,对,s是是Student,student也是也是(isa)Personstudy(s);/对对study(p);/错错!p不都是不都是Student这只是对公有继承才是正确的。仅当这只是对公有继承才是正确的。仅当Student类是从类是从Person类中公有地派生出来的时候,类中公有地派生出来的时候,C+才具有像上面所才具有像上面所描述的那种性质。描述的那种性质。公共继承和公共继承和“isa”的等价性看起来很简单,但在实际应的等价性看起来很简单,但在实际应用中并不容易。有时自己的直觉会产生误导。譬如说,企用中并不容易。有时自己的直觉会产生误导。譬如说,企鹅是鸟是一
34、件事,鸟会飞是另一件事鹅是鸟是一件事,鸟会飞是另一件事。企鹅是鸟,但不能企鹅是鸟,但不能从鸟会飞误导出企鹅会飞。从鸟会飞误导出企鹅会飞。如果已有一个如果已有一个address类,它描述地址这个概念,现在要类,它描述地址这个概念,现在要建立一个建立一个worker类,它描述职工这个概念,每个职工都有类,它描述职工这个概念,每个职工都有一个住址,一个住址,worker类可以有两种定义形式:使用继承或对类可以有两种定义形式:使用继承或对象成员。如果采用继承,可以按如下形式定义:象成员。如果采用继承,可以按如下形式定义:classworker:publicaddress/;如果在如果在worker类中
35、定义一个类中定义一个address类的类对象成员,可类的类对象成员,可以按如下形式定义:以按如下形式定义:classworkerprivateaddressworkerAddr;/;使用继承的方法是说职工是一个地址,使用对象的方法声使用继承的方法是说职工是一个地址,使用对象的方法声称职工包含有一个地址属性。这两种说法中后者的说法是称职工包含有一个地址属性。这两种说法中后者的说法是正确的,即地址只能作为职工的一个属性,在概念上,它正确的,即地址只能作为职工的一个属性,在概念上,它们之间没有联系,所以上例中第们之间没有联系,所以上例中第2种的描述形式是合理的。种的描述形式是合理的。这就是分层实现。
36、这就是分层实现。由此可见,类用于描述一类对象的共同特性,不同种类的由此可见,类用于描述一类对象的共同特性,不同种类的对象之间的联系使用继承来表示,对象所具有的属性使用对象之间的联系使用继承来表示,对象所具有的属性使用类的成员来表示。分层就是一种处理过程,它通过让分层类的成员来表示。分层就是一种处理过程,它通过让分层的类里包含被分层的类的对象作为其数据成员,以便把一的类里包含被分层的类的对象作为其数据成员,以便把一个类建立在另一些类之上。例如:个类建立在另一些类之上。例如:classString;/字符串字符串classAddress;/某人居住的地方某人居住的地方classPhoneNumbe
37、r;classPersonprivate:Stringname;/被分层的对象被分层的对象Addressaddress;/被分层的对象被分层的对象PhoneNumbervoiceNumber;/被分层的对象被分层的对象PhoneNumberfaxNumber;/被分层的对象被分层的对象public:;在这个例子中,在这个例子中,Person类要被分层到类要被分层到String、Address和和PhoneNumber这几个类的上面,这是因为它里面含有这这几个类的上面,这是因为它里面含有这些类型的数据成员。分层也可以叫做包含、嵌入或者聚合。些类型的数据成员。分层也可以叫做包含、嵌入或者聚合。公有
38、继承的意思是公有继承的意思是“isa”。与此相反,分层的意思是指与此相反,分层的意思是指“has-a(有一个)有一个)”或者或者“is-implemented-in-terms-of(是按是按实现的)实现的)”。上面的。上面的Person类表示的是一种类表示的是一种“has-a”的关系。一个的关系。一个Person对象有一个名字、一个地址对象有一个名字、一个地址和用于语音及传真通信的两个电话号码。我们不会说一个和用于语音及传真通信的两个电话号码。我们不会说一个人就是一个字符串,或者一个人就是一个地址,但会说一人就是一个字符串,或者一个人就是一个地址,但会说一个人有一个字符串,并且有一个地址等。
39、个人有一个字符串,并且有一个地址等。许多人做这种分辨时困难不大,搞不清许多人做这种分辨时困难不大,搞不清“isa”和和“has-a”之间区别的人相对来说也很少见。稍微有些伤脑筋的是之间区别的人相对来说也很少见。稍微有些伤脑筋的是“isa”和和is-implemented-in-terms-of之间的差异,这可通之间的差异,这可通过例子来加深理解。在过例子来加深理解。在7.5节中,将给出分别使用这两种节中,将给出分别使用这两种方法设计的例子。方法设计的例子。3.公有继承存取权限表公有继承存取权限表派生类一般都使用公有继承。使用基类的有基类本身、派派生类一般都使用公有继承。使用基类的有基类本身、派
40、生类、对象和外部函数,对派生类而言,使用它的有派生生类、对象和外部函数,对派生类而言,使用它的有派生类本身、对象和外部函数。类本身、对象和外部函数。类中可以使用三种成员,表类中可以使用三种成员,表7.1总结了它们之间的关系。总结了它们之间的关系。由此可见,保护类型的成员(数据成员和成员函数)介于由此可见,保护类型的成员(数据成员和成员函数)介于私有和公有之间。对派生类来讲,它的作用与私有和公有之间。对派生类来讲,它的作用与public成员成员一样。对类的对象、外部函数以及不属于本类系之外的类一样。对类的对象、外部函数以及不属于本类系之外的类来说,它与来说,它与private成员一样,均是不可访
41、问的,从而保持成员一样,均是不可访问的,从而保持了类的封装性。了类的封装性。“访问控制访问控制”决定着基类各成员在派生类中的访问权决定着基类各成员在派生类中的访问权限,上面讨论了最常用的公有继承方式,下面将讨限,上面讨论了最常用的公有继承方式,下面将讨论论private和和protected继承方式。继承方式。4.私有派生私有派生通过私有派生,基类的私有和不可访问成员在派生类中通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定有成员,派生类的对象不
42、能访问继承的基类成员,必须定义公有的成员函数作为接口。更重要的是,虽然派生类的义公有的成员函数作为接口。更重要的是,虽然派生类的成员函数可通过自定义的函数访问基类的成员,但将该派成员函数可通过自定义的函数访问基类的成员,但将该派生类作为基类再继续派生时,这时即使使用公有派生,原生类作为基类再继续派生时,这时即使使用公有派生,原基类公有成员在新的派生类中也将是不可访问的。下面就基类公有成员在新的派生类中也将是不可访问的。下面就来看一个私有派生的例子。来看一个私有派生的例子。【例【例7.4】私有派生的类继续派生的例子。私有派生的类继续派生的例子。#includeusingnamespacestd;
43、classPointprivate:intx,y;public:Point(inta,intb)x=a;y=b;voidShow()coutx=x,y=yendl;classRectangle:privatePointprivate:intH,W;public:Rectangle(inta,intb,inth,intw):Point(a,b)H=h;W=w;voidShow()Point:Show();coutH=H,W=Wendl;classTest:publicRectanglepublic:Test(inta,intb,inth,intw):Rectangle(a,b,h,w)voidS
44、how()Rectangle:Show();在这个例子中,基类的公有成员函数在这个例子中,基类的公有成员函数Show通过私有派生通过私有派生成了派生类的私有成员函数,成了派生类的私有成员函数,Test虽然是公有派生,但它虽然是公有派生,但它已经无法使用基类的已经无法使用基类的Show函数,即不能通过函数,即不能通过“Point:Show();”方式使用方式使用Point类的类的Show函数,这就函数,这就彻底切断了基类与外界的联系。私有派生的这一特点不利彻底切断了基类与外界的联系。私有派生的这一特点不利于进一步派生,因而实际中私有派生用得并不多。于进一步派生,因而实际中私有派生用得并不多。5.
45、保护派生保护派生派生也可以使用派生也可以使用protected。这种派生使原来的权限都降一这种派生使原来的权限都降一级使用:级使用:private变为不可访问;变为不可访问;protected变为变为private;public变为变为protected。因为限制了数据成员和成员函数的因为限制了数据成员和成员函数的访问权限,所以用得较少。它与访问权限,所以用得较少。它与private继承的区别主要在继承的区别主要在下一级的派生中。如果将上例下一级的派生中。如果将上例Rectangle改为保护继承方改为保护继承方式,则在式,则在Test类中可以使用基类的类中可以使用基类的Show函数,则下面函数
46、函数,则下面函数的定义是正确的:的定义是正确的:classTest:publicRectanglepublic:Test(inta,intb,inth,intw):Rectangle(a,b,h,w)voidShow()Point:Show();Rectangle:Show();/可可/以使用基类以使用基类Point的成员的成员;7.3多重继承多重继承一个类从多个基类派生的一般形式是:一个类从多个基类派生的一般形式是:class类名类名1:访问控制:访问控制类名类名2,访问控制访问控制类名类名3,.,访问控制访问控制类名类名n./定义派生类自己的成员定义派生类自己的成员;类名类名1继承了类名继
47、承了类名2到类名到类名n的所有数据成员和成员函数,的所有数据成员和成员函数,访问控制用于限制其后的类中的成员在类名访问控制用于限制其后的类中的成员在类名1中的访问权中的访问权限,其规则和单一继承情况一样。多重继承可以视为是单限,其规则和单一继承情况一样。多重继承可以视为是单一继承的扩展。一继承的扩展。【例【例7.5】演示多重继承的例子。演示多重继承的例子。#includeusingnamespacestd;classAprivate:inta;public:voidsetA(intx)a=x;voidshowA()couta=aendl;classBprivate:intb;public:vo
48、idsetB(intx)b=x;voidshowB()coutb=bendl;classC:publicA,privateBprivate:intc;public:voidsetC(intx,inty)c=x;setB(y);voidshowC()showB();coutc=cendl;voidmain()Cobj;obj.setA(53);obj.showA();/输出输出a=53obj.showC();/输出输出b=58c=55类类C从类从类A公有派生,因此,类公有派生,因此,类A的公有成员(保护成员)的公有成员(保护成员)在类在类C中仍是公有的(保护的)。类中仍是公有的(保护的)。类C从
49、类从类B私有派生,类私有派生,类B的所有成员在类的所有成员在类C中是私有的。这些成员在派生类中的中是私有的。这些成员在派生类中的可访问性和单一继承中讨论的一样。可访问性和单一继承中讨论的一样。类类B被私有继承,因此,类被私有继承,因此,类C负责维护类负责维护类B的数据成员值和的数据成员值和显示,所以在显示,所以在showC和和setC中分别调用中分别调用类类B的成员函数的成员函数showB和和setB。使用使用obj.setB(5)和和obj.showB()都是错误的。都是错误的。7.4二义性及其支配规则二义性及其支配规则7.4.1二义性和作用域分辨符二义性和作用域分辨符对基类成员的访问必须是
50、无二义性的,如使用一个表达对基类成员的访问必须是无二义性的,如使用一个表达式的含义能解释为可以访问多个基类中的成员,则这种对式的含义能解释为可以访问多个基类中的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。基类成员的访问就是不确定的,称这种访问具有二义性。【例【例7.6】访问具有二义性的例子。访问具有二义性的例子。#includeusingnamespacestd;classApublic:voidfunc()couta.funcendl;classBpublic:voidfunc()coutb.funcendl;voidgunc()coutb.guncendl;classC