在前面的章节,我们学会了如何编写一个完整的类。然而,面向对象的优势还没有被完全体现出来。特别是在编写一些相似的类时,可能会造成很多的浪费。本章就将以一个文字游戏为例,向大家介绍类的继承问题。
在一个角色扮演类游戏(RPG)中,可能有各种不同职业的玩家,比如剑士、弓箭手和法师。虽然他们的职业不同,却有着一些相似之处:他们都具有生命值(Health Point——HP)、魔法值(Magic Point——MP)、攻击力(Attack Point——AP)、防御力(Defense Point——DP)、经验值(Experience——EXP)和等级(Level——LV)。虽然他们有着相似之处,但又不完全相同:剑士和弓箭手都具有普通攻击的技能,只不过剑士用的是剑,而弓箭手用的是弓箭。
这样看来,我们有麻烦了。如果只用一个类来描述三种不同职业的玩家,肯定无法描述清楚。毕竟这三种职业不是完全相同的。如果用三个类来描述这三种职业,那么三者的共同点和内在联系就无法体现出来,并且还造成了相同属性和功能的重复开发。
我们需要有一种好的方法,既能把剑士、弓箭手和法师的特点描述清楚,又能减少重复的开发和冗余的代码。在C++中,有一种称为继承的方法,使我们可以用一种已经编写好的类来扩写成一个新的类。新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。继承实质上是源于人们对事物的认知过程:从抽象概念到具体事物。下面我们就来看看剑士、弓箭手和法师的逻辑关系:
在上图中玩家是一个抽象的概念,剑士、弓箭手和法师是具体的事物。任何一个玩家都具有生命值、魔法值等属性,具有发动普通攻击和特殊攻击的能力。不同职业的玩家在发动普通攻击和特殊攻击时,有着不同的效果。
如果你不太玩游戏,或者对剑士、弓箭手没有什么概念,那么我们再来看看学生这个例子。学生是一个抽象的概念,具体的有本科生、中学生、小学生等。任何一个学生都具有姓名、身高、体重、性别等属性,具有学习的能力。不同阶段的学生在学习时,内容会有所不同。小学生学习四则运算,中学生学习代数几何,本科生学习高等数学。如下图:
为了描写小学生、中学生、本科生,我们可以写三个不同的类,但是会造成部分属性和功能的重复开发。我们也可以先设计一个学生类,描述出各种学生的共同属性或功能,然后再针对不同种类的学生做细节的修改。显然,第二种做法更为省力、合理。
如果有一个类,我们可以将其实例化,成为若干个对象。另外,如果我们希望对这个类加以升级改造,我们可以将这个类继承,形成子类(或者称为派生类),被继承的类则称为父类(或者称为基类)。实例化和继承是一个类的两种发展方向。继承能够减少我们开发程序的工作量,提高类的重用性。
如果我们把编写一个类看作是一次生产,那么产品(即编写出来的类)可以有两种用途:一种是将产品直接使用,相当于将类实例化;另一种是将产品用于再生产,相当于将类继承。类在这种不断的“再生产”中变得更为强大、健全。在第15章中,我们曾将链表结点类的实例对象作为链表类的成员数据。这称为对象的组合,它与类的继承也是完全不同的概念。继承(Inheritance)是概念的延续,子类和父类一般都是概念扩展的关系,我们通常把这种关系称为“是”关系。比如:本科生是学生,自行车是交通工具。而对象的组合是因功能需求产生的从属关系,我们通常把这种关系称为“有”关系。比如:链表有一个头结点,电脑有一个中央处理器等等。
关于如何更合理地设置类以及类与类之间关系的问题,会在软件工程这门课中作更详尽的介绍。