C程序设计课件(第2章).ppt

上传人:sccc 文档编号:5336456 上传时间:2023-06-27 格式:PPT 页数:47 大小:300.01KB
返回 下载 相关 举报
C程序设计课件(第2章).ppt_第1页
第1页 / 共47页
C程序设计课件(第2章).ppt_第2页
第2页 / 共47页
C程序设计课件(第2章).ppt_第3页
第3页 / 共47页
C程序设计课件(第2章).ppt_第4页
第4页 / 共47页
C程序设计课件(第2章).ppt_第5页
第5页 / 共47页
点击查看更多>>
资源描述

《C程序设计课件(第2章).ppt》由会员分享,可在线阅读,更多相关《C程序设计课件(第2章).ppt(47页珍藏版)》请在三一办公上搜索。

1、2023/6/27,1,第2章 继承与派生,本章学习重点掌握内容:继承的概念派生类的建立及继承的方式各种继承方式下基类成员的访问机制派生类的构造函数和析构函数多重继承虚基类,2023/6/27,2,第2章 继承与派生,2.1 继承与派生的基础知识 2.2 类的继承方式 2.3 派生类的构造函数与析构函数 2.4 基类与派生类的转换 2.5 多重继承 2.6 虚基类2.7 综合应用实例,2023/6/27,3,2.1 继承与派生的基础知识,2.1.1 继承与派生的基本概念 现实世界中,许多事物之间的并不是孤立存在的,它们存在共同的特性,有细微的差别,可以使用层次结构描述它们之间的关系。例如交通工

2、具的层次结构如图2.1所示:,2023/6/27,4,2.1.1 继承与派生的基本概念,C+通过类派生(Class Derivation)的机制支持继承(Inheritance)。允许程序员在保持原有类特性的基础上进行扩展,增加功能,派生出新类。继承是面向对象程序设计中的代码复用的最重要的手段之一。被继承的类称为基类(Base Class)、父类或超类(Superclass),而新产生的类称为派生类(Derived Class)或子类(Subclass)。基类和派生类的集合称作类继承层次结构(Hierarchy),继承呈现了面向对象程序设计的层次结构。,2023/6/27,5,2.1.1 继承

3、与派生的基本概念,一个新类从已有的类获得其已有的特性称为继承。通过继承,新类获得了父类的所有数据成员和成员函数,并可以添加自己的数据成员和成员函数。一个基类可以派生出很多的子类,一个子类也可以作为另一个新类的基类,因此基类和子类是相对而言的。继承的方式有以下2种:单一继承和多重继承。,2023/6/27,6,2.1.1 继承与派生的基本概念,单一继承和多重继承,请注意图中箭头的方向,本书约定,箭头表示继承的方向,由子类指向基类。,2023/6/27,7,2.1.2 派生类的定义,定义派生类的一般格式为:class 派生类名:继承方式 基类名private:成员表1;/派生类增加或重写的私有成员

4、protected:成员表2;/派生类增加或重写的保护成员public:成员表3;/派生类增加或重写的公有成员;,2023/6/27,8,2.1.2 派生类的定义,其中:基类名是已声明的类,派生类名是新生成的类名;继承方式规定了如何访问从基类继承的成员。继承的方式包括:私有继承(private)、保护继承(protected)、公有继承(public)。不同的继承方式下,派生类继承的父类成员的访问权限是不同的。继承方式可以省略不写,默认的继承方式为私有继承(private);派生类的定义中包括子类新增加的成员和继承父类需要重写的成员。新添加的成员是派生类对基类的发展,说明派生类新的属性和方法;

5、派生类继承了父类的数据成员和成员函数,有时继承来的成员函数需要改进,以满足新类的实际需要。C+允许在派生类中重新声明和定义这些成员函数,使这些函数具有新的功能,称之为重写或覆盖。重写函数起屏蔽、更新作用,取代基类成员,完成新功能。,2023/6/27,9,2.1.2 派生类的定义,【例2.1】已知盒子CBox类,用继承与非继承两种不同的方法定义彩色盒子CColorbox类。分析:盒子类(Cbox)具有长、宽和高,成员函数SetLength()、SetWidth()和SetHeight()分别设置盒子的长、宽和高,成员函数Volume()计算盒子的体积。彩色盒子除具有以上特性外,还有一个数据成员

6、color表示盒子的颜色,相应的成员函数SetColor()用于设置彩色盒子的颜色。,2023/6/27,10,2.1.2 派生类的定义,非继承的方式,分别定义CBox类和CColorbox类盒子类的定义:代码见备注彩色盒子类的定义:代码见备注,2023/6/27,11,2.1.2 派生类的定义,使用派生类定义:class CColorbox:public CBox/公有继承/新增的私有数据成员public:void SetColor(int c)/新增的成员函数 color=c;private:int color;利用继承机制产生类比第一种简单多了,但功能一样。派生类CColorbox公有继

7、承Cbox类,它包括基类CBox类的全部数据成员(length,width,height)和成员函数(SetWidth、SetHeigh和SetWidth),但访问权限发生了变化。并且添加自己的新成员数据成员color和成员函数SetColor()。,2023/6/27,12,2.1.3 派生类的生成,仔细分析派生新类这个过程,实际是经历了以下步骤:首先继承基类的成员,不论是数据成员,还是成员函数,除构造函数与析构函数外全部接收,全部成为派生类的成员。第二步是重写基类成员。当基类成员在派生类的应用中不合适时,可以对继承的成员加以重写。如果派生类声明了一个与基类成员函数相同的成员函数时,派生类中

8、的新成员则屏蔽了基类同名成员,类似函数中的局部变量屏蔽全局变量。称为同名覆盖(Override)。第三步定义新成员。新成员必须与基类成员不同名,是派生类自己的新特性。派生类新成员的加入使得派生类在功能上有所发展。这一步是继承与派生的核心特征。第四步是重写构造函数与析构函数。因为派生类不继承基类的构造函数与析构函数,并且派生类的需要对新添加的数据成员进行必要的初始化,所以构造函数与析构函数需要重写。,2023/6/27,13,2.2类的继承方式,派生类中包含基类的成员和派生类自己增加的成员,那么这两部分的成员关系和访问权限该如何确定呢?在继承机制中,并不是简单的把基类的私有成员直接作为派生类的私

9、有成员,把基类的公有成员直接作为派生类的公有成员。派生类继承的基类成员访问权限由继承方式来控制。继承方式有三种:public(公有)继承、protected(保护)继承和private(私有)继承。不同的继承方式,决定了从基类继承来的成员的访问权限。下面分别介绍不同继承方式下,派生类成员的访问权限。,2023/6/27,14,2.2.1 公有继承,当定义一个派生类时,将基类前的继承方式指定为public,则称为公有派生(或公有继承)。采用公有继承方式时,基类的公有成员和保护成员的访问权限在派生类中不变。而基类的私有成员在派生类中是不可访问。但它仍然是基类的私有成员,如果需要在派生类中引用继承基

10、类的私有成员,那么需要通过基类的公有或保护的成员函数访问。,【例2.3】演示公有继承方式下,不同成员的访问权限。程序代码见备注:,2023/6/27,15,2.2.1 公有继承,【例2.4】公有派生方式下如何访问继承的基类原有私有数据成员。程序代码见备注:,2023/6/27,16,2.2.2 私有继承,当定义一个派生类时,将基类前的继承方式指定为private,则称为私有继承。用私有继承方式建立的派生类称为私有派生类,其基类成为私有基类。采用私有继承方式时,私有基类的公有成员和保护成员在私有派生类中成为私有成员。即派生类成员可访问它们,而派生类外不可访问它们。基类的私有成员在派生类中成为不可

11、访问的成员。私有继承基类成员的访问权限如表2-2所示。私有继承的意义是将基类中原来能被外部访问的成员隐藏起来,不让外界引用。,【例2.5】私有继承演示。,2023/6/27,17,2.2.2 私有继承,由上例可以看到私有继承方式:(1)不能通过派生类对象(box1)引用从私有继承过来的任何成员。如box1.set(3,5,6);或box1.length=100。(2)在派生类内部(如派生类的成员函数),不可以访问基类的私有成员(如length=len,length为基类的私有成员),但可以访问基类的公有和保护成员(如height=h,height为基类的保护成员)。(3)如果派生类需要访问基类

12、的私有成员,可以通过派生类的成员函数调用基类的公有成员函数实现如:void set_1(double len,double w,double h,int c)set(len,w,h);/基类的公有成员函数由上可以看出,私有派生的限制太多,一般不经常使用。,2023/6/27,18,2.2.3 保护继承,当定义一个派生类时,将基类前的继承方式指定为protected,则称为保护继承。在保护继承中,基类的公有成员和保护成员成为派生类的保护成员,在派生类中可以直接访问,但在派生类外不能直接访问任何基类成员的。基类中的私有成员成为派生类的不可访问成员,在派生类中不可直接访问。保护继承基类成员的访问权限

13、如表2-3所示。保护继承的意义是将基类的公有成员也保护起来,不让类外部任意访问。,2023/6/27,19,继承的方式有三种,使用不同继承方式,基类的成员在派生类中的访问权限也不同。不同继承方式下基类成员在派生类的访问权限总结如表2-4所示。,2.2.4 继承方式的总结和比较,2023/6/27,20,2.3 派生类的构造函数与析构函数,派生类的成员是由基类中的数据成员和派生类中新增的数据成员共同构成。而在继承机制下,构造函数不能够被继承。因此,对继承过来的基类成员的初始化工作也得由派生类的构造函数完成。也就是说在定义派生类的构造函数时,既要初始化派生类新增数据,又要初始化基类的成员。所以,在

14、定义派生类的构造函数时,有两步需要做:编写代码完成自己的数据成员进行初始化调用基类构造函数使基类数据成员得以初始化。,2023/6/27,21,2.3.1 简单派生类的构造函数,单一继承的构造函数的定义形式为:派生类名:派生类构造函数名(参数总表):基类构造函数名(参数名表)派生类新增成员的初始化语句;定义派生类的构造函数时,在构造函数的参数总表中包括基类构造函数所需的参数和派生类新增的数据成员初始化所需的参数。冒号后面基类构造函数名(参数名表),表示要调用基类的构造函数。,【例2.6】演示派生类的构造函数执行顺序。,2023/6/27,22,2.3.2 析构函数,析构函数的功能是做善后工作,

15、析构函数无返回类型也没有参数,情况比较简单。在派生过程中,基类的析构函数不能继承,如果需要析构函数的话,要在派生类中重新定义。派生类析构函数定义格式与非派生类无任何差异,只要在函数体内把派生类新增一般成员处理好就可以了。而对基类成员的善后工作,系统会自己调用基类的析构函数来完成。如果没有显示的定义析构函数,系统会自动生成一个默认的析构函数,清理工作就是靠它们来完成的。析构函数各部分执行次序与构造函数相反,首先对派生类新增成员析构,然后对基类成员析构。,2023/6/27,23,2.3.3 复杂派生类的构造函数和析构函数,一个派生类中新增加的成员可以是简单的数据成员,也可以是类对象。派生类可以是

16、单一继承,也可以是多重继承。假如派生类是多重继承,并且新增数据成员有一个或多个类对象,那么派生类需要初始化的数据有三部分:继承的成员、新增类对象的成员和新增普通成员。这种复杂派生类的构造函数定义如下:派生类名:派生类构造函数名(总参数表):基类构造函数名1(参数表1),基类构造函数名2(参数表2),子对象名1(参数表n),子对象名2(数表n+1)派生类新增普通数据成员的初始化;,2023/6/27,24,2.3.3 复杂派生类的构造函数和析构函数,派生类构造函数的调用顺序如下:基类构造函数。按它们在派生类定义中的先后顺序,依次调用。子对象的构造函数。按它们在派生类定义中的先后顺序,依次调用。派

17、生类的构造函数。复杂派生类的析构函数,只需要编写对新增普通成员的善后处理,而对类对象和基类的善后工作是由类对象和基类的析构函数完成的。析构函数的调用顺序与构造函数相反。,【例2.7】复杂继承举例。,2023/6/27,25,2.3.3 复杂派生类的构造函数和析构函数,【例2.7】复杂继承举例。,2023/6/27,26,2.3.3 复杂派生类的构造函数和析构函数,在派生类构造函数使用中应注意以下问题:(1)派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有缺省的构造函数或者根本没有定义构造函数。当然,基类中没有定义构造函数,派生类调用基类的缺省构造函数。(2)当基类的构

18、造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径。在有的情况下,派生类构造函数的函数体可能为空,仅起到参数传递作用。,2023/6/27,27,2.4 基类与派生类的转换,基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。具体表现在以下几个方面:派生类对象可以向基类对象赋值可以用子类(即公用派生类)对象对其基类对象赋值。如Cbox与CColorbox:CBox box;/定义基类CBox 对象boxCColorbox colorbox;/定义类Ccolorbox

19、的对象colorboxbox=colorbox;/用派生类B对象b1对基类对象a1赋值在赋值时舍弃派生类自己的成员。实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。,2023/6/27,28,2.4 基类与派生类的转换,(2)派生类对象可以向基类对象的引用进行赋值或初始化CBox box;/定义基类CBox 对象boxCColorbox colorbox;/定义派生类Ccolorbox的对象colorboxCBox r2与colorbox具有相同的起始地址。,2023/6/27,29,2.4 基类与派生类的转换,(3)派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向

20、基类对象的指针变量也可以指向派生类对象。例如:CBox box;/定义基类CBox 对象boxCColorbox colorbox;/定义类CBox的公用派生类Ccolorbox的对象colorboxCBox*pt=/调用colorbox.SetHeight()函数,2023/6/27,30,2.5 多重继承,前面主要介绍了单一继承,仅仅提到多重继承的概念。在现实世界中,很多时候一个类会有两个或两个以上的基类。例如沙发床,既继承了床的特性又继承了沙发的特性。沙发床的多重继承结构图如图2.7所示。C+中,定义派生类时,派生类有两个或多个基类称为多重继承。,2023/6/27,31,2.5 多重继

21、承,2.5.1 多重继承的定义多重继承可以看作是单一继承的扩展,多重继承的定义格式如下:class 派生类名:继承方式1 基类名1,继承方式2 基类名,public:新增加的公有成员;protected:新增加的保护成员;private:新增加的私有成员;;,2023/6/27,32,2.5 多重继承,多重继承派生类的构造函数格式如下:派生类名:派生类构造函数名(总参数表):基类名1(参数表1),基类名2(参数表2)派生类构造函数体其中,总参数表中各个参数包含了其后基类的各个分参数表。多重继承下派生类的构造函数与单一继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,

22、派生类的参数必须包含完成所有基类初始化所需的参数。,2023/6/27,33,2.5.1 多重继承的定义,【例2.8】设计沙发床类。分析:床类可以用来睡觉Sleep(),沙发类可以用来看电视WatchTV()。沙发床具有床和沙发两者的特特性,沙发床还有自己的特性折叠FoldOut()。因此先定义床类和沙发类,沙发床类由这两个类派生。然后添加沙发床自己的特性。程序代码如下:,2023/6/27,34,2.5.1 多重继承的定义,沙发床类继承了床类和沙发类,具有了沙发和床的特性,因此可以使用派生类对象ss调用床的Sleep()成员函数,完成睡觉功能;通过调用沙发WatchTV()成员函数完成看电视

23、功能;调用新增的FoldOut()可以完成折叠与打开功能。派生类对象调用构造函数的顺序是先基类的构造函数再派生类的构造函数,有两个基类(Cbed和Csofa)时,基类构造函数调用的顺序是按照声明派生类时基类的排列顺序来进行。,2023/6/27,35,2.5.2 多重继承中的二义性问题,多重继承反映了现实生活中的情况,使一些复杂的问题简单化,提高了程序开发效率。但多重继承中存在两类二义性问题:1.调用不同基类中相同成员时产生的二义性 派生类的多个基类之间出现相同的成员,则在派生类中访问此成员时会出现二义性。,2023/6/27,36,2.5.2 多重继承中的二义性问题,解决多重继承中调用不同基

24、类中相同成员时产生的二义性可以使用以下方法:(1)使用域作用符解决二义性问题可以使用:域作用符对此成员函数加以区分:ob1.CBed:SetWeight(100);/调用基类Cbed的函数成员SetWeight()ob1.CSofa:SetWeight(200);/调用基类CSofa的函数成员SetWeight()使用域作用符可以消除编译时二义性。程序员需要知道类的继承层次信息,加大程序开发的复杂度。,2023/6/27,37,2.5.2 多重继承中的二义性问题,(2)覆盖函数同名隐藏(覆盖)派生类继承了两个基类的print()函数,派生类在自己的构造函数中又重写了print()函数。如果在主

25、函数如下:void main()CSleeperSofa ob1;/继承了两个基类的特性ob1.print();/正确,print()是覆盖函数不存在二义性ob1.print();语句能通过编译,这是因为派生类新增的成员函数print()覆盖了基类中的同名成员,这点与局部变量屏蔽全局变量类似。可以使用域作用符调用基类的成员函数。如ob1.CBed:print(),则调用基类Cbed的print()。,2023/6/27,38,2.5.2 多重继承中的二义性问题,注意:在不是虚函数的情况下,如果派生类中新增的成员函数与基类的某一成员函数同名,则该函数会隐藏基类中所有该函数的重载函数。#inclu

26、declass CA public:void f(int x)coutthe f(int)of CA!endl;void f()coutthe f()of CA!endl;class CB:public CA public:void f()coutthe f()of CB!endl;void main()CB b;b.f();b.f(0);,2023/6/27,39,2.5.2 多重继承中的二义性问题,2派生类中访问公共基类成员时产生的二义性 派生类有多个基类,而这多个基类又从同一个基类派生,则在派生类中访问公共基类成员时会出现二义性。此时引入虚基类解决。,2023/6/27,40,2.5.2

27、 多重继承中的二义性问题,【例2.9】一个公共基类在派生类中产生的二义性问题。类B与类C由类A公有派生,而类D由类B与类C公有派生(如图2.9所示),则类D中将包含类A的两个拷贝。这种同一个基类在派生类中产生多个拷贝不仅多占用了存储空间,而且可能会造成二义性问题。,2023/6/27,41,2.6 虚基类,在多重继承中,虚基类保证从不同路径继承过来的公共基类成员在内存中就只有一个拷贝,所以可解决同名基类成员产生的二义性问题。2.6.1 虚基类的定义虚基类的定义是在派生类的定义过程中定义的,语法格式如下:class 派生类名:virtual 继承方式 基类类名.;其中,virtual 关键字只对

28、紧随其后的基类名起作用。上述定义使得基类为派生类的虚基类,定义了虚基类之后,虚基类的成员在派生过程中和派生类一起维护同一个内存拷贝。,【例2.10】用虚基类的方法解决公共基类成员产生的二义性。,2023/6/27,42,2.6 虚基类,(1)采用虚基类后,在D类中只有唯一的数据成员x;所以在建立D类的对象后,调用Print()输出x时,不产生二义性。(2)A类为虚基类以后,公共基类A的构造函数仅被执行一次。因为具有虚基类的派生类构造函数与一般派生类构造函数有所不同。在例2.10中,D的构造函数中增加一个给虚基类的初始化列表项A(a),而给两个直接基类B,C初始化列表项B(a,b),C(d,e)

29、仍然保留。C+为了保证虚基类构造函数仅被执行一次,规定在创建对象的派生类构造函数中优先调用虚基类的构造函数,并在执行后,忽略直接基类初始化列表对虚基类的构造函数的调用。保证虚基类的构造函数仅被执行一次。,2023/6/27,43,2.6 虚基类,2.6.2 虚基类及其派生类构造函数执行顺序在同时具有虚基类和非虚基类的的派生类对象的创建中,首先是虚基类的构造函数被调用,并按它们声明的顺序调用,接着是非虚基类的构造函数按它们声明的顺序调用,其次是对象成员的构造函数。最后是派生类自己的构造函数被调用。【例2.12】在采用虚基类的多重继承中,分析构造函数与析构函数的执行顺序。,2023/6/27,44

30、,2.7 综合应用实例,【实例一】编写一个学生信息和教师信息输入和显示的管理程序。学生信息有编号、姓名、性别、生日和班级名、各门课程的成绩,教师信息有编号、姓名、性别、生日和职称、部门。要求将学生和教师信息的共同特性设计成一个类(CPerson类),作为学生CStudent类和教师CTeacher类的基类。分析:根据题目要求,可以将编号、姓名、性别、生日作为基类CPerson类的数据成员,为实现输入和显示这些信息,在基类CPerson类设计Input()和PrintPersonInfo()这两个成员函数实现。在派生类CStudent类和CTeacher类中主要考虑自有信息输入和显示即可。这三个

31、类的类图如图2.12所示。,2023/6/27,45,2.7 综合应用实例,【实例一】编写一个学生信息和教师信息输入和显示的管理程序。,2023/6/27,46,2.7 综合应用实例,【实例二】编写程序,实现某个公司的工资管理。该公司主要有四类人员:经理、技术员、销售员和销售经理。要求存储这些人员的编号、姓名,并且能计算各类人员月工资和显示该员工全部信息。他们的月收入如下计算:经理:月工资固定8500;技术员:每小时150元计算;销售员:按当月销售额的7%提取;销售经理:每月固定4000,另按其部门每月的销售额的0.5%提成。分析:依题意设计一个基类Employee,其中有3个数据成员no(编号)、name(姓名)、salary(月工资)和成员函数pay(计算月工资)、display(显示月工资)及构造函数。由基类Employee派生出经理、技术员、销售员、销售经理这4个类。建立各个类层次结构如图2.13所示:,2023/6/27,47,2.7 综合应用实例,【实例二】编写程序,实现某个公司的工资管理。该公司主要有四类人员:经理、技术员、销售员和销售经理。要求存储这些人员的编号、姓名,并且能计算各类人员月工资和显示该员工全部信息。,

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 建筑/施工/环境 > 农业报告


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号