C程序设计语言(李雁妮)第11章PPT推荐.ppt
《C程序设计语言(李雁妮)第11章PPT推荐.ppt》由会员分享,可在线阅读,更多相关《C程序设计语言(李雁妮)第11章PPT推荐.ppt(146页珍藏版)》请在冰豆网上搜索。
,“相似”无非是既有共同点,又有差别。
相似性有两种类别。
(1)内涵的相似性:
概念之间具有一般与特殊的关系,它们之间存在着共性。
例如雇员(Employee)与经理(Manager)之间的关系就属于此类,一个Manager是一个Employee,而且是一个特殊的Employee。
(2)结构的相似性:
概念之间具有相似的结构表示。
例如数组类array和vector,虽然vector是一种特殊的动态数组,但其结构与array相同。
那么,如何利用语言中相应的机制来表示这种相似性呢?
(1)如果将相似的事物用不同的类型来表示,则只能够表示其差别,体现不了它们之间存在着共性这一事实;
另外,在不同的类中其共性的表示也可能不一致,当扩充维护过程中需要对其共性部分进行修改时,就面临着保持一致性的问题。
(2)如果将相似的事物用相同的类型来表示(例如把可能的特征都定义上去,再设法进行投影),则体现其差别就十分困难,且失去了类型化的支持。
一旦需扩充和修改(哪怕只是涉及差别部分),也将影响用此种类型表示的所有其它事物。
显然上述两种做法都不能较准确地表达类型之间的相似关系。
C+提供的继承机制可供我们方便地表达类之间的共性。
本章将要讨论面向对象程序设计中两个极为重要的机制:
继承(Inheritance)和多态(Polymorphism)。
面向对象程序设计的关键之一就是通过继承来实现软件的重用(Reuse),通过多态来实现软件的自适应性和灵活性。
11.2子类/派生类11.2.1子类/派生类与继承的基本概念首先,让我们从一实际问题出发,来看一看如何正确地表达两个概念(雇员类Employee与经理类Manager)之间的相似性。
方式1将相似的事物用不同的类型表示,但类中重复定义共同的特征。
(注意:
两个类中阴影部分是相同的。
)类Manager除了具有和Employee相同的属性外,还拥有它自己的属性:
所管理的雇员列表listgroup和它的级别shortlevel。
方式2将相似的事物用不同的类型来表示,类中将共同的特征归纳成一个抽象特征对象成员来表示。
上述两种共性的表示方式对读者特别是细心的读者来讲一目了然,但这种共性关系对编译器和运行环境而言却一无所知!
由于用了两个(没有指明存在共性关系的)类型(类Employee和Manager)来表示它们之间的共性关系,因此一个Manager*不是一个Employee*。
所以,一个Manager*不仅不能用于Employee*出现的场合,而且对于上述的应用而言,Manager*根本插入不到经理类Manager所管理的雇员列表group对象中。
C+的派生类机制通过定义Manager是从Employee继承而来的,从而显式地定义了两者之间的共性。
继承机制明确表明:
一个Manager就是一个Employee,其差异在于一个Manager是一个特殊的Employee。
classManager:
publicEmployee/类Manager自Employee继承而来listgroup;
shortlevel;
/.;
上述定义中,Manager称为基类(BaseClass)或父类(Superclass),相应地,Employee称为派生类(DerivedClass)或子类(Subclass)。
类Manager不仅继承了类Employee的所有成员(所继承的成员无须在子类中定义),而且拥有它自己的成员listgroup和shortlevel。
通常,把一个派生类自它的父类继承(Inherit)而来这种继承关系称为继承性(Inheritance),如图11.1所示。
图11.1类Employee和Manager之间的继承关系,注意:
与我们常规理念不同的是,派生类通常大于父类。
一个子类不仅继承了其父类的所有数据成员与方法,而且还拥有它自身的数据成员与方法,因此,子类与父类之间满足:
sizeof()sizeof(),11.2.2子类对象的存储结构子类/派生类对象的存储结构与父类/基类对象的存储结构存在着“粘接”关系,即子类对象的存储结构是其父类对象成员粘接上自己的数据成员,其“粘接”关系如图11.2所示。
子类与父类之间的关系实际上是一种“是一”(is_a)关系,即子类是一个父类,如图11.3所示。
由于父类与子类之间为“是一”关系,因此,一个子类对象一定是一个父类对象。
指向父类的指针和引用可用于子类指针和引用出现的场合,但其逆命题不成立。
图11.2子类对象与父类对象之间存储结构的粘接性,图11.3子类与父类之间的“是一”关系,11.2.3子类中的成员当类Manager继承Employee时,目的除了要表示它们之间的相似关系以外,更重要的是让Manager的操作集利用继承关系的描述而自然地发生扩张。
对Employee数据结构的继承只是手段而不是目的。
利用继承机制达到了子类Manager操作集的自然扩张,以及对其操作集的使用不需要了解父类Employee操作集的细节。
例如:
根据上述定义,类Manager是Employee的子类,因此,Manager继承了父类Employee的所有成员(构造函数与析构函数除外)。
Manager类的数据成员是:
11.2.4子类的构造与析构函数由于子类继承了父类的数据成员和方法,因此,子类的实例生成和消除与父类的实例生成和消除自然相关。
一个子类对象的构造过程是:
先构造其父类对象,然后再构造自己。
其析构过程与构造过程相反,即先析构自己,然后再析构父类对象。
因此,子类的构造函数参数表中的参数应包含两部分:
调用基类的对应构造函数所需的参数;
子类构造函数所需的参数。
子类对象在析构时,子类的析构函数会自动调用基类的析构函数。
C+语法规定:
在子类中对父类构造函数的调用只能通过初始化表的方式进行。
classEmployeestringfirst_name,family_name;
charmiddle_initial;
shortdepartment;
/.public:
voidprint()const;
stringfull_name()const;
总之,子类对象的构造是自顶向下的,即首先构造父类对象,然后构造子类中的对象成员,最后构造子类本身;
其对象的析构次序与构造次序相反,即首先析构子类对象,然后析构子类中的对象成员,最后析构基类对象。
在一个类中可能有多个对象成员,另外类层次亦可能为多层,此时,对象成员和基类的构造次序与它们声明的次序相同,析构时与其声明的次序相反。
对象的构造与析构次序如下例所示:
11.2.5子类对象拷贝通过前面的学习我们已经知道:
当一个类自另一个类继承而来时,子类和父类之间就存在着“是一”关系,即一个子类对象亦是一个父类对象,但其逆命题不成立。
因此,子类对象可以赋值或拷贝给父类对象,但此时仅将子类对象中相应的父类对象值部分赋值或拷贝给父类对象。
11.2.6public、protected和private继承C+中提供了三种继承父类的方式,即:
公有继承(public);
保护继承(protected);
私有继承(private,C+中缺省的继承方式)。
一般常用公有继承,保护继承和私有继承只在一些特殊的场合中使用。
类的每个成员都具有访问控制权限,在第9章中已经指出访问控制的作用是:
使得类的成员函数确实构成了这个类的操作集;
易于进行出错定位;
用成员函数的声明组成这个类的接口;
有利于掌握一个类型的使用方式。
具有各种访问控制的成员对外所具有的能见度如图11.4所示。
由于有了继承机制,类成员的访问控制就涉及派生类以不同方式从基类继承的、具有不同能见度的那些成员。
关于派生类有怎样的外部能见度与内部能见度问题,下面我们就不同的继承情况分别进行讨论。
图11.4具有各种访问控制的成员的对外能见度,1public公有继承classB:
publicA/.;
当公有继承时,子类B从父类A中继承下来的所有成员的访问权限不变。
对类B而言,其内部能见度为父类的protected、public成员,以及自己的具有各种访问控制的所有成员;
类B的外部能见度为父类的public成员及自身的public成员。
B类所继承的父类的protected成员及自身的protected成员只对B类的子类可见。
2protected保护继承classB:
protectedA/.;
当保护继承时,子类B从父类A中继承下来的private及protected成员的访问权限不变,但父类的public变为protected。
类B的外部能见度仅为自身的public成员。
B类所继承的protected、public(已改变为protected)及自身的protected成员只对B类的子类可见。
3private公有继承classB:
privateA/私有继承,C+规定可缺省/.;
当私有继承时,子类B将父类A中继承下来的protected及public成员的访问权限全部改为private。
对类B而言,其内部能见度为父类的protected、public成员,以及自己的所有成员;
类B的protected成员只对其子类可见。
表11.1总结了在不同的继承下,基类成员访问控制的变化情况及子类对基类成员的内、外能见度。
由于C+有三种继承方式,所以子类的内、外能见度同时与继承方式和被继承者的能见度相关。
11.3虚函数与多态性11.3.1类型域当采用指针或引用时,一个子类对象可以被当作一个父类对象使用。
当某一函数接口参数的类型为父类型的指针或引用时,由于其实参可为父类或其子类的指针或引用,因此操作时常常需要对传入的实参对象的类型进行判断。
voidprint_employee(constEmployee*e)if(*e的类型是Employee)/打印Employee对象的信息else,if(*e的类型是Manager)/打印Manager对象的信息else/.在上述打印雇员信息的函数print_employee中,由于参数是Employee*类型,因此它可以接受Employee或Manager对象的地址,故在函数体中应对实参传入的对象类型作出判断,之后进行相应的处理。
为实现正确的操作,要求对象本身带有类型信息。
使对象带有类型信息的方法是在对象所属的类中设立类型标志信息。
仍以Employee和Manager类为例,此时,在Employee类中定义一个标识所属类别的类型域成员type(枚举类型的变量),然后对Employee和Manager对象在其成员上赋相应的值,使对象自身带有类型信息。
在类定义中采用类型域的方法是不应当提倡的,因为编译程序无法检查出诸如将Employee:
M赋给Employee:
type这样的错误。
另外,编译程序亦无法检查出未在所有的构造函数中将类型域变量都赋同样的、正确的值的错误。
对于大型软件,人工保持