将关键字加在析构函数前面,则称该析构函数为虚析构函数。如果一个基类的析构函数被说明为虚析构函数,则派生类中的析构函数也是虚析构函数,不管它是否使用关键字virtual进行说明。虚析构函数的目的在于使用delete运算符删除一个对象时,可以使用动态联编方式来选择析构函数,以保证正确的析构函数被执行。
【例5.10】没有虚析构函数,派生类对象没有析构。
【例5.11】声明虚析构函数后派生类对象正常析构。
上述程序输出结果为:
class A
class B
A 类对象析构完成
B类对象析构完成
A 类对象析构完成
我们的任务是“用面向对象程序设计思想设计一个计算器程序,要求输入两个数和运算符号,得到结果”。看过这个题目,可能大多数的学生第一反应为,这题太简单,我会做,然后在五分钟后给出如下结构化设计的实现程序1:
1.结构化耦合在一起的代码
上面的程序完美地解答了“现阶段的问题”。但请想想我们为什么要学面向对象,我们学面向对象就是为了解决一个用原有技术、体系或手段就可以解决的问题吗?显然不是。记住,面向对象的威力在于复用,在于可以实现更高效的代码,在于可以更好地应对未来的变化。上面的代码将计算与显示耦合在了一起,一旦计算规程或显示要求发生变动,就不得不修改代码。
在我们学习面向对象程序设计时,必须要练习使用面向对象的分析设计的编程思想,在解决问题时,要开始考虑通过封装、继承、多态把程序的耦合度降低,来使得程序更加的灵活,容易修改,并且易于复用。复用不是Ctrl+C和Ctrl+V,不停地使用Ctrl+C和Ctrl+V 是非常不好的编程习惯,因为当代码中重复的代码多到一定程度,维护的时候就是一场灾难。编程有一个原则,就是用尽可能的办法去避免重复。以本任务来说,首先应考虑哪些是业务逻辑,哪些是界面逻辑,首先将它们的耦合度降下来。只有分离开,才可以达到容易维护和扩展。
2.业务逻辑与界面逻辑分开的代码
这样编写代码就做到了业务与界面分离,在做其它应用程序的计算器,就可以复用运算类了。此处用到了面向对象程序设计思想中的封装,把运算操作封装为一个运算类,采用静态方法的方式供用户调用。
对于程序2是否可以灵活地修改和扩展呢?例如,当需要加一个开平方的运算时,是否只需要修改运算类,在switch语句中加一个case分支就行了呢?如果只是为了加入一个开平方运算,而让其它运算都需要重新参与编译,代价较高。甚至在修改程序过程中,如果不小心将加改成减,那将带来致命的错误。为了添加一个算法却需要将整个算法组程序都公开,那将是非常危险的。需要一种能够把减加乘除等运算分离,提取运算的共性,同时也保留各自运算的各性,修改其中一个不影响另外几个,增加算法也不影响其他代码的设计方式。面向对象的继承可以解决此类的问题。首先,设计一个抽象的运算类,包括运算数A、运算数B和运算符以及运算方法;接着,设计实现运算类的各个派生类,如减、加、乘、除运算类,在派生类中重写运算方法,如:加就返回运算数A+运算数B 等;然后,再利用多态产生的条件,设计一个负责在运行过程中确定具体运算对象的类;最后在界面层(主函数)中调用即可。具体看下面的实现程序。
3.体现面向对象程序设计思想的代码
综合实训
继承与多态性
【实训目的】
1.掌握继承、基类和派生类的概念;
2.掌握初始化基类成员的方法;
3.掌握派生类对基类的继承;
4.学习虚函数和纯虚函数的定义与使用方式;
5.理解抽象类的概念,学习如何用指针指向其他的派生类,实现多态性;
6.掌握抽象类的定义与使用方式,并注意指针的用法;
7.学习如何使用虚函数、纯虚函数、抽象类和实现类的多态性。
【实训内容】
1.定义一个个人信息类Person,其数据成员包括有姓名、性别、出生年月。并以Person为基类定义一个学生的派生类Student,增加描述学生的信息:班级、学号、专业、英语成绩和数学成绩。再由基类Person定义一个职工的派生类Employee,增加描述职工的信息:部门、职务、工资。编写程序实现学生与职工信息的输入与输出。
2.定义Time类,其数据成员包括有小时、分钟、秒。定义Date类,其数据成员包括年月、日。再设计一个Birthtime类,它继承了Time类和Date类,并且还有一项出生孩子的名字Childname,同时设计主程序显示一个小孩的出生时间和名字。
3.编写一个程序计算正方体、球体和圆柱体的体积和表面积。请定义一个抽象类,并用纯虚函数的方式实现上述要求。
【实训设计】
1.参考程序
分析:本题目考察继承和派生的使用方法,派生类成员函数的使用方法。
程序代码如下:
2.参考程序
分析:本题目考察继承和派生的使用方法和多继承的知识,Birthtime类继承了Time类和Date类,重点考察派生类构造函数的参数传递和使用。
3.参考程序
分析:本题目考察抽象类和纯虚函数的使用方法。首先定义抽象类shape,在其中定义纯虚函数area和volume,在派生类中重新定义area和volume函数,用于求取表面积和体积。
程序代码如下:
项目小结
本项目中,我们学习了面向对象程序设计中两个非常重要的内容:继承和多态。
继承是面向对象程序设计中程序复用的根本前提。可以在已有类的基础上再定义一个新类,新类中保留原类提供的有用程序,而只专注于新需要添加的功能。原有类称为新类的基类,新类称为原类的派生类。基类只有一个称为单继承,基类多于一个称为多继承。C++是一个支持多继承的程序设计语言,但不是所有的面向对象程序设计语言都是支持多继承的。在C++中,继承的方式又分为三种,即公有继承、保护继承和私有继承。它们的不同主要体现在基类中不同访问控制符下的数据成员和成员函数在派生类所处的位置不同上。公有继承条件下,基类的保护成员和公有成员被继承下来,在派生类中访问属性保持不变;保护继承条件下,基类的保护成员和公有成员被继承下来,在派生类中访问属性都变为保护;私有继承条件下,基类的保护成员和公有成员被继承下来,在派生类中访问属性都变为私有。当然在实际面向对象编程时,为了照顾类型兼容规则,一般使用的都是公有继承。在学习继承时,要特别注意派生类的构造函数的编写,如果基类对象的构造函数是有参数的,则必须在派生类构造函数中将参数传递给基类构造函数。同时要记住,派生类对象的在创建时,构造函数的调用顺序是:先基类构造函数,再子对象构造函数,最后才是自己的构造函数。而析构函数的调用顺序刚好相反。为了消除多重继承产生的二义性问题,提供了虚基类的方法。
多态是面向对象程序设计中程序中应对变化的最佳解决办法,是面向对象程序设计的精髓所在。使用多态的前提条件是在类设计时要遵循赋值兼容规则,即派生类对象能完美地替代基类对象。除此之外C++中多态的实现还需要满足两个条件:(1)基类中要有虚函数,即用virtual关键字说明;(2)调用虚函数的操作必须在定义时使用指向基类对象的指针或为基类对象的引用,而在调用时传入派生类对象的指针或引用派生类对象,或者由成员函数调用虚函数。使用多态实际上就是要使用动态联编。因为虚函数基本无意义,为了省略定义虚函数体的过程,提出了纯虚函数的概念。有了纯虚函数之后又有了抽象类的概念。有了抽象类后,因为抽象对象是无意义的,所以为防止抽象对象建立又提出了一大堆的限制条件。这是一个因果关系,大家在学习时要认真体会,加强理解。至于虚析构函数,是为了保证在使用delete运算符删除一个派生类对象时,可以使用动态联编方式来选择析构函数,以保证正确的析构函数被执行。
课后练习
一、填空题
1.继承的3种方式是______、_____和________。
2.如果类A 继承了类B,则类A 被称为_________类,类B被称为______类。
3.在保护继承方式下,基类的public成员成为派生类的________成员,基类的protected成员成为派生类的________成员。
4.当一个派生类中含有子对象时,该派生类的析构函数中应包含_________的析构函数、子对象类的______和_________的析构函数。
5.派生类的构造函数的成员初始化列表中可以包含的初始化项有_____________、_________、_____________和_______________。
6.静态联编支持的多态性称为_________多态性,它是在_________时进行的;动态联编支持的多态性称为________多态性,它是在________时进行的。
7.虚函数是一种________成员函数。说明方法是在函数名前加关键字_________。虚函数具有_________性,在基类中被说明的虚函数,具有相同说明的函数在派生类中自然是虚函数。
8.含有________的类称为抽象类。它不能定义对象,但可以定义________和________。
二、选择题
1.下列关于继承的描述中,错误的是( )。
A.继承是重用性的重要机制
B.C++语言支持单重继承和双重继承
C.继承关系不是可逆的
D.继承是面向对象程序设计语言的重要特性
2.下列关于基类和派生类的描述中,错误的是( )。
A.一个基类可以生成多个派生类
B.基类中所有成员都是它的派生类的成员
C.基类中成员访问权限继承到派生类中不变
D.派生类中除了继承的基类成员还有自己的成员
3.下列关于派生类的描述中,错误的是( )。
A.派生类至少有一个基类
B.一个派生类可以作另一个派生类的基类
C.派生类的构造函数中应包含直接基类的构造函数
D.派生类默认的继承方式是public
4.派生类的对象可以直接访问的基类成员是( )。
A.公有继承的公有成员 B.保护继承的公有成员
C.私有继承的公有成员 D.公有继承的保护成员
5.下列描述中,错误的是( )。
A.基类的protected成员在public派生类中仍然是protected成员
B.基类的private成员在public派生类中是不可访问的
C.基类public成员在private派生类中是private成员
D.基类public成员在protected派生类中仍是public成员
6.派生类构造函数的成员初始化列表中,不能包含的初始化项是( )。
A.基类的构造函数 B.基类的子对象
C.派生类的子对象 D.派生类自身的数据成员(www.daowen.com)
7.下列关于子类型的描述中,错误的是( )。
A.在公有继承下,派生类是基类的子类型
B.如果类A 是类B的子类型,则类B也是类A 的子类型
C.如果类A 是类B的子类型,则类A 的对象就是类B的对象
D.在公有继承下,派生类对象可以初始化基类的对象引用
8.下列关于多继承二义性的描述中,错误的是( )。
A.一个派生类的多个基类中出现了同名成员时,派生类对同名成员的访问可能出现二义性
B.一个派生类有多个基类,而这些基类又有一个共同的基类,派生类访问公共基类成员时,可能出现二义性
C.解决二义性的方法是采用类名限定
D.基类和派生类中同时出现同名成员时,会产生二义性
9.下列关键字中,用来说明虚函数的关键字是( )。
A.inline B.operator
C.virtual D.public
10.下列的成员函数中,纯虚函数是( )。
A.virtual void f1()=0 B.void f1()=0;
C.virtual void f1(){} D.virtual void f1()==0;
11.含有一个或多个纯虚函数的类称为( )。
A.抽象类 B.具体类
C.虚基类 D.派生类
12.下列关于虚函数的描述中,错误的是( )。
A.虚函数是一个成员函数
B.虚函数具有继承性
C.静态成员函数可以说明为虚函数
D.在类的继承的层次结构中,虚函数是说明相同的函数
13.下列各种类中,不能定义对象的类是( )。
A.派生类 B.抽象类
C.嵌套类 D.虚基类
14.下列关于抽象类的描述中,错误的是( )。
A.抽象类中至少应该有一个纯虚函数
B.抽象类可以定义对象指针和对象引用
C.抽象类通常用作类族中最顶层的类
D.抽象类的派生类必定是具体类
15.一个类的层次结构中,定义有虚函数,并且都是公有继承,在下列情况下,实现动态联编的是( )。
A.使用类的对象调用虚函数
B.使用类名限定调用虚函数,其格式如下:< 类名> ::< 虚函数名>
C.使用构造函数调用虚函数
D.使用成员函数调用虚函数
16.下列关于动态联编的描述中,错误的是( )。
A.动态联编是函数联编的一种方式,它是在运行时来选择联编函数的
B.动态联编又可称为动态多态性,它是C++语言中多态性的一种重要形式
C.函数重载和运算符重载都属于动态联编
D.动态联编只是用来选择虚函数的
三、判断题
1.派生类只继承基类中的公有成员和保护成员,而不继承私有成员。( )
2.多重继承是指一个基类派生出多个派生类的情况。( )
3.单重继承是指派生类只有一个基类的情况。( )
4.派生类还可以作基类派生出新的派生类。( )
5.派生类中成员的访问权限与基类的继承方式有关。( )
6.派生类中只包含直接基类的成员,不包含间接基类的成员。( )
7.基类中成员在派生类中都是可以访问的。( )
8.私有继承中基类的私有成员在派生类中还是私有的。( )
9.保护继承方式下基类的保护成员在派生类仍是保护成员。( )
10.派生类的对象和派生类的派生类对派生类成员的访问权限是一样的。( )
11.派生类的默认构造函数不包含有直接基类的构造函数。( )
12.派生类是基类的子类型。( )
13.多重继承派生类的构造函数中应包含所有直接基类的构造函数。( )
14.只要是成员函数就可以说明为虚函数,因为虚函数是一种成员函数。( )
15.虚函数可以被类的对象调用,也可以被类的对象指针和对象引用调用。( )
16.动态联编指的是在运行期间来选择不同类的虚函数。( )
17.虚函数是实现动态联编的充分必要条件。( )
18.含有纯虚函数的类称为抽象类,与抽象类相对应的是具体类。( )
19.抽象类可以定义对象,不可以定义对象指针和对象引用。( )
20.成员函数和构造函数调用虚函数都可以实现动态联编。( )
21.析构函数可以说明为虚函数,而构造函数说明为虚函数没有意义。( )
22.抽象类的派生类一定是具体类。( )
23.一个抽象类中可以包含有多个纯虚函数,一个派生类中也可以包含多个虚函数。( )
四、简答题
1.在继承关系中,派生类中包含基类所有成员,基类是否也包含派生类的部分成员?
2.构造函数不能继承,派生类的构造函数中是否应包含直接基类的构造函数和所有间接基类的构造函数?
3.派生类的析构函数中不包含直接基类的析构函数,对吗?
4.派生类的对象可以给基类对象赋值吗?
5.多重继承的二义性可以避免吗?
6.多态性中对函数的选择从时间上来区分有哪两种方式?
7.有虚函数是否就一定是动态联编?非虚函数是否就一定是静态联编?
8.在多层次的继承结构中,基类与派生类中存在着虚函数,这时调用虚函数就一定实现动态联编吗?
资源推荐
1.C++网站 http://www.cplusplus.com
2.C++类库参考 https://msdn.microsoft.com/zh-cn/library/cscc687y.aspx
3.百度传课 http://www.chuanke.com/course/72351176561000448________2.html
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。