1、C+程序设计基础,第一章基础知识第二章基本控制结构程序设计第三章函数第四章类与对象第五章指针与数组第六章动态内存分配第七章继承与多态第八章流类库和输入输出,第七章继承与多态7. 1 继承与派生的概念 7. 2 派生类的构造函数与析构函数 7. 3多重继承 7. 4虚基类 7. 5多态性与虚函数,7. 1 继承与派生的概念 层次概念是计算机的重要概念。对类分层,提供类型子类型的关系,这是通过继承机制获得的。通过类派生(Class Derivation)的机制来支持继承。被继承的类型称为基类(Base Class),而新产生的类为派生类(Derived Class)。基类和派生类的集合称作类继承层
2、次结构(Hierarchy)。,7. 1. 1 类的派生与继承 由基类派生出派生类定义的一般形式为class 派生类名:访问限定符 基类名1,访问限定符基类名,访问限定符基类 名 private:成员表; /派生类增加或替代的私有成员 public:成员表; /派生类增加或替代的公有成员 protected: 成员表; /派生类增加或替代的保护成员 ;,其中基类,基类,是已声明的类。如果一个派生类可以同时有多个基类,称为多重继承,这时的派生类同时得到了多个已有类的特征。多重继承的处理非常复杂,而且易出错,将放在后面讨论。一个派生类只有一个直接基类的情况称为单一继承。在这里先讨论单一继承。 在派
3、生类定义的类体中给出的成员称为派生类成员,它们是新增加的数据和函数成员。这些新增加的成员是派生类对基类的拓展,它们给派生类添加了不同于基类的新的属性和功能。派生类成员包括新添加的,也包括通过屏蔽作用、取代基类成员的更新成员。,在派生类的声明中,还有访问控制,亦称为继承方式,这是对基类成员进一步的限制。访问控制也是三种:公有(Public)方式,保护(Protected)方式和私有(Private)方式,亦称公有继承、保护继承和私有继承。如不显式给出访问控制关键字,则默认为私有继承。,编制派生类时可分步。首先吸收基类的成员,不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收,全部成为派
4、生类的成员。第二步是改造基类成员,当有的基类成员在新的应用中不合适,可以加以改造,如果派生类声明了一个和某个基类成员同名的新成员(当然如是成员函数,参数表也必须一样,否则是重载),派生类中的新成员就屏蔽了基类同名成员,类似函数中的局部变量屏蔽全局变量,称为同名覆盖(Override)。第三步是发展新成员,新成员必须与基类成员不同名,派生类新成员的加入保证派生类在功能上有所发展。从一般的学校在册人员类,发展到学生类必然要增加学生类所独有的新成员。也只有这一步才是继承与派生的核心特征。第四步是重写构造函数与析构函数,因为派生类不继承这两种函数,不管原来的函数是否可用一律重写。,7. 1. 2公有派
5、生与私有派生 在派生类的定义中,基类前所加的访问限定符,对由基类继承来的成员的访问做了进一步的限制。这访问有两方面含义:派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作),从派生类对象之外对派生类对象中的基类成员的访问。 ()公有派生。当定义中基类前的访问限定符为公有(Public),则称公有派生(或公有继承)。基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。,即派生类成员可以直接访问公有和保护的基类成员,但只能通过基类中的公有成员函数去访问基类的私有成员。这一点有些类似从类对象之外访问类对象的公有和私有成员。但保护成员不同,从类对象之外是不能直接访问
6、保护成员的,而在派生类中是可以直接访问基类的保护成员的。在派生类对象之外只能直接访问派生类对象中基类的公有成员。 公有派生是最常用的派生方式。,()私有派生。对私有派生类,基类中的公有成员和保护成员在派生类中成为私有成员。派生类成员仍可直接访问公有和保护的基类成员,不能直接访问基类的私有成员。这点与公有派生一样。但是从类对象之外是不能访问其基类的任何成员,只能通过派生类的公有成员函数去访问,也就是基类成员全部成为私有的。 保护派生极少用到。,当将派生类当作基类再派生出新的派生类(第三代)时,第一代积累中的私有成员在第三代中就更加看不到了。所以,将数据成员定义成保护的,则能够传递到各级后代派生类
7、中可以直接操作。(血脉传承),7. 2派生类的构造函数与析构函数 派生类的构造函数的定义形式为:派生类名 : 派生类名(参数总表):基类名(参数表),基类名(参数表), 基类名(参数表),成员对象名(成员对象参数表),成员对象名(成员对象参数表) /派生类新增成员的初始化 /所列出的成员对象名全部为新增成员对象的名字,这个构造函数的定义,与包含成员对象类的构造函数相类似。在构造函数的参数总表中,包括需初始化的新增一般成员数据的全部参数,新增成员对象需初始化的全部参数和基类中需初始化的成员数据的全部参数。也就是说后面所有参数表都是它的互不相交的子集。冒号后的基类名,成员对象名的次序可以随意,这里
8、的次序与调用次序无关。,派生类构造函数各部分的执行次序为: ()调用基类构造函数,按它们在派生类声明的先后顺序,依次调用。 ()调用新增成员对象的构造函数,按它们在类定义中声明的先后顺序,依次调用。 ()派生类的构造函数体中的操作。 在派生类构造函数中,只要基类不是使用缺省构造函数都要显式给出基类名和参数表。,7. 3 多重继承 7. 3. 1 冲突,上图中,在职学生类派生于学生类和职工类。两个基类中的数据成员id和函数成员Show设为公有属性,那么,在职学生类中,就有了继承自学生类的id和职工类的id,两个成员同名。设有在职学生类对象a,a.id就带来了冲突。解决办法是使用作用域运算符。a.
9、Stu:id及a.Emp:id。共有函数成员Show也需要如此处理。,7. 3. 2 支配 在继承的过程中,派生类有可能出现与基类同名的成员。例如,在上图在职学生类中添加一函数成员,取名也是Show。a.Show()执行的是派生类的成员函数,不是基类的成员函数。这就是只配规则。,7. 3. 3 赋值兼容规则 赋值的兼容规则指的是在公有派生的情况下,派生类对象和基类对象间的赋值关系。 有以下三种情况(假设类derived由类base派生): 派生类对象可以赋值给基类对象。 derived d;base b;b=d;, 派生类对象可以初始化基类引用。derived d;base 也就是说,派生类对
10、象中从基类继承过来的那一部分成员可以有一个基类的别名br。实用上就是说函数的参数如果是基类引用,则实参可以是派生类对象。, 派生类对象的地址可以赋给基类的指针。derived d;base *pb= 当然,只能用基类指针引用派生类对象中从基类继承过来的那一部分成员。,7. 4 虚基类7. 4. 1 在实际应用中,类的层次结构往往是单继承和多继承的混合结构,如下图:,在职学生类的成员结构如图 : 其中具有了两份Person类的拷贝,这是不允许的。为了防治这样的情况发生,引入了虚基类概念。将公共基类Person类说明为虚基类,这样,无论该基类如何派生,在派生类中只能保留一份拷贝。,虚基类在派生时进
11、行声明,其声明的形式为: class 派生类名: virtual 访问限定符 基类类名; 或: class派生类名: 访问限定符 virtual 基类类名;,class Stu: virtual public Person 学生类新增成员; 同样需对职工类定义: class Emp: virtual public Person 职工类新增成员; 这样由学生类和职工类派生出的在职学生类的定义为: class Stu_Emp: public Stu, public Emp 在职学生类新增成员;,7. 4. 2 虚基类的初始化 如果在虚基类中定义了带参数的构造函数,而且没有定义默认的构造函数,则在其
12、所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化列表直接显式调用虚基类的构造函数对其进行初始化。 Person类的直接派生类的构造函数没有特别的地方。在职学生类为间接派生,它的构造函数为:,Stu_Emp(char sId, int n1, double sc, int n2 ,double sa): Person(sId), Stu(sId,n1, sc), Emp(sId,n2, sa) /新类型的构造函数,必须直接显式调用虚基类构造函数 没有虚基类概念时的构造函数为: Stu_Emp(int n1, double sc, int n2 ,double sa):Stu(
13、n1, sc), Emp(n2, sa) ,7. 5 多态性与虚函数 利用多态性技术,可以调用同一个函数名的多个函数,分别实现完全不同的功能。在中有两种多态性:编译时的多态性和运行时的多态性。前者是通过函数的重载和运算符的重载来实现的。运算符重载,是根据运算对象的不同在编译时就可确定执行什么样的运算。运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态确定。,这种多态性是通过类继承关系和虚函数(Virtual Function)来实现的,其目的也是建立一种通用的程序。通用性是程序追求的主要目标之一。7. 5. 1 虚函数的定
14、义 虚函数是一个类的成员函数,定义虚函数的格式如下:Virtual 返回类型函数名(参数表); 关键字Virtual指明该成员函数为虚函数。,当某一个类的一个类成员函数被定义为虚函数时,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。当在派生类中重新定义虚函数时,不必加关键字Virtual,但重新定义时不仅要同名,而且它的参数表和返回类型必须全部与基类中的虚函数一样。如果参数表有变化,就不是虚函数而是函数重载。,(1) 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。 (2) 静态成员函数为所有同一类对象所共有,不受限于某个对象,所以不能作为虚函数。 (
15、3) 实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。,(4)析构函数可定义为虚函数,构造函数不能定义为虚函数。因为在构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性。 exam141.cpp和exam142.cpp说明了虚函数的应用特性。,7. 5. 2 纯虚函数和抽象类 纯虚函数(Pure Virtual Function)是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实
16、现,其实现依赖于不同的派生类。 定义纯虚函数的一般格式为: virtual 返回类型函数名(参数表) = 0;,含有纯虚函数的基类是不能用来定义对象的。纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。 当把一个类的构造函数或析构函数的访问限定定义为protected时,也是抽象类。使用这样的类不能直接创建一个对象,因为无法调用构造函数或析构函数,所以不能建立对象。 抽象类只能做为派生用的基类。,定义纯虚函数必须注意: ()定义纯虚函数时,不能定义虚函数的实现部分。即使是函数体为空也不可以,函数体为空就可以执行,只是什么也不做就返回,而纯虚函数不能调用。 ()“”表明程序员将不定义该函数,函数声明是为派生类保留一个位置。“”本质上是将指向函数体的指针定为NULL。 ()在派生类中必须有重新定义的纯虚函数的函数体,这样的派生类才能用来定义对象。,