下面来看一个在游戏程序中使用多态的例子。啊,用于说明多态是如何起到程序的可扩充性的。 我们以我十几年前玩的一个游戏叫做魔法门之英雄无敌为例,游戏里面有 各种各样的怪物,双方都可以指挥几个怪物跟别人去打, 那很显然在这样一个游戏里面呢每种怪物都应该是 有一个类来代表,而每一个具体的怪物就会是一个对象, 比方说用这个CSoldier代表士兵,CPhonex代表凤凰,CDragon代表龙等等, 那么每一种怪物呢,它们都要和敌人作战, 怪物之间互相攻击,攻击的是就会做出动作,啊,这些动作当然都是用成员函数来实现啦。 比如挥舞一把宝剑,啊,放出一个气功炮等等,那怪物呢它还会 被别人攻击的时候也会受伤,受伤也会做出各种各样的动作, 会流血,会抖一下,如果血没了就会倒地死去。啊, 这些当然都是通过成员函数来做啦。 那这些怪物它有基本的成员变量,就是它的攻击力和生命值, 那这样的游戏通常会需要升级,啊,添加新的怪物, 比方说我们要加一个新的怪物叫做雷鸟,就这个东西啊, 那怎么样编程?我们这游戏原来怎么写 才会使得升级的时候要增加一个新怪物,然后我这个代码量会增加的比较少呢? 这就是我们需要解决的这个问题。 那下面我们通过多态的实现方式和非多态实现方式的对比 就能够看出多态的实现方式在 添加新的怪物的时候,原来的程序锁需要做的改动会比非多态的iii程式要少很多。 那不管用不用多态,我们这个程序的基本思路都是这样的,就是我们要为每一个怪物 编写Attack,FightBack或者Hurted的成员函数。 这个Attack成员函数用来表现攻击的动作,啊,它会攻击某个怪物, 然后Attack还要调用被攻击怪物的Hurted成员函数, 这个被攻击怪物的Hurted成员函数就会减少 被攻击怪物的生命值,然后这个 Attack成员函数呢还需要被攻击怪物的FightBack成员函数, 这样就能够接受被攻击怪物的反击, 那Hurted函数就减少自身的生命值并且表现出受伤的动作, FightBack成员函数表现反击这样的一个动作, 然后调用被反击对象的Hurted成员函数使这个被反击对象受伤, 那我知道了, 各类的怪物都有一些共同的特点,比方说它有生命值,还有这个攻击力,还有上述的 Attack,FightBack,Hurted成员函数,因此我们就应该设置一个基类CCreature, 这个CCreature概括了所有怪物的共同特点, 然后我们具体的某一种怪物比如说龙啊,狼啊什么的,我们这些类呢都从CCreature类 派生而来。CCreature是个基类它派生出CDragon代表龙,CWolf代表狼,CSoldier代表士兵, 这是基本的思路。那接下来我们先看,如果我不使用多态这个程序改怎么写。 当然我们需要有一个CCreature类,对吧,它概括了所有类的共同特点。 还有Power攻击力LifeValue生命值两个成员变量,然后呢 CCreature类就是这样了,但是其他的类都是从CCreature类 派生而来,比如CDragon这个类它是从CCreature类派生而来的, 那CDragon这个类里面有Attack这个成员函数,但是呢一个Attack成员函数 不够用,为什么啊,因为一个CDragon类的对象它要攻击各种各样的其他种类的这个怪物, 比方说要攻击狼这个怪物,那它就需要一个Attack成员函数, 这个Attack成员函数呢接收一个CWolf新的指针作为参数, 这个指针就会指向被攻击的那一只狼,那在这个Attack里面, 先写一些代码,表现出那个很炫的攻击的动作, 接下来,就要调用 被攻击对象的Hurted成员函数。我们看到被攻击对象是由pWolf 这个指针所指向的,我们在这里调用被攻击对象的Hurted成员函数。 并且以Power,nPower就是攻击力作为参数传进去,这样就能够使得被攻击对象受伤, 那你传进去的这个nPower越大呢,那个被攻击的对象受到的伤害可能就越大。 然后如果被攻击对象如果没有被打死的话我们还要用 被攻击对象的FightBack成员函数使被攻击对象对自己进行反击, 那怎么样对自己进行反击呢?就是我们以this指针作为参数 去调用这个FightBack,this指针就是指向Attack这个成员函数 所作用的那个对象,也就是攻击的发起者,对吧,那我们现在以this指针作为参数去调用FightBack, 那在被攻击对象的FightBack里面,就反击,就有了目标,就是这个this指针所指向的对象。 那这个FightBack成员函数是需要写很多个的。 攻击狼的Attack就是这样的。那要攻击鬼的呢?那Attack就是这样的。它里面的结构差不多,但是表现攻击动作的代码 它有可能会针对被攻击对象而有所不同。 当然最关键的还是这个参数的类型,就不一样。它这个是Attack. 然后我们再看这个Hurted。Hurted就是如果一个怪物被 别人攻击了,或者被别人反击了,那它的Hurted成员函数就被调用, 参数就是攻击力,那在Hurted成员函数里面呢首先要 写一些代码来表现受伤的动作,受伤啊,流血啊,惨叫一声等等, 甚至倒地死去,然后在这里面呢,要减掉 这个生命值, 假设我们的规则就是把生命力减去这个攻击力, 那如果生命力减低为0以下了,那下面可能还会有一些 动作表现出来,表现它倒地死去。啊,这是Hurted,需要一个就够了。那FightBack 就用来在一个对象被别人攻击的时候对攻击者表示以反击的, 那攻击者有各种各样的,那我们要反击的 对象当然也是各种各样不同类型的,所以跟Attack原理类似。FightBack我们也要写多个 FightBack,iii的成员函数,用来反击不同类型的攻击者, 比如说我们要反击狼这种攻击者,那么FightBack的参数就是 CWolf*,那在FightBack里面首先我们要表现这个反击的这个动作, 然后呢我们再调用被反击对象的Hurted成员函数使得被反击对象 受伤,那这个时候由于反击 它的攻击力就不像主动攻击那么大,所以我们可以把这个 攻击力这个参数,把它设为主动攻击的参数的力量的一半, 啊,这个参数,决定对被反击者的伤害有多大。 那总而言之啦,你要是有n种怪物的话 这个CDragon类就会有n个Attack成员函数,啊,因为龙和龙之间也会互相打的啊, 然后还会有n个FightBack成员函数,对于其他的类来说也是这样的, 那 如果这个游戏版本升级, 添加了新的这个怪物雷鸟,CThunderBird这个类,这个程序改动就会比较大。 到底有多大呢?那显然我们是需要再写一个CThunderBird的类的,这个是少不了的, 但问题是所有已经存在的类我们都要往里头添加两个新的成员函数, Attack和FightBack, 这两个成员函数的参数是这种新的类型的CThunderBird*, 这样我们已有的类才能去攻击这个雷鸟, 也才能被雷鸟反击。 那我们每一个类都需要改动,当然这个程序的改动就是比较大的。 那怪物种类很多的时候这工作量实在就让人抓狂啊, 那接下来我们来看看这个神奇的多态的实现方法。 那当然也是需要一个基类, CCreature,这个CCreature概括了所有各种类的 共同特点,比方说这个LifeValue生命值,Power攻击力。 然后还有Attack,FightBack,Hurted这三个成员函数, 但是我们要注意到这个时候Attack只需要一个就够了,这三个成员函数呢全都是虚函数 这点很重要啊,那Attack只需要一个就够了,Attack成员函数里面的参数呢是基类的指针CCreature*这种类型的, 然后FightBack它的参数也是基类的指针 CCreature*,实际上,不但基类只有一个Attack和FightBack,实际上 所有的从CCreature派生出来的那些类也都只有一个Attack和FightBack,我们以CDragon类为例来看。 它有一个Attack成员函数, 参数是CCreature*,然后有Hurted和Fightback 所以他们都是虚函数。那这个CDragon呢具体的函数怎么写呢? Attack接收一个CCreature*参数, 首先在这里面要表现攻击别人的这个代码,那我们知道 龙攻击别人的这个那个形式它可能是喷火,狼攻击别人可能就撞上去咬,这些动作都是不一样的, 所以在基类里面写一个Attack是不够用的, 必须在每一个派生类里面都要写Attack才可以。 那对于CDragon这个类来说,Attack里面先表现攻击动作, 然后再调用被攻击对象的Hurted成员函数,被攻击对象就是p这个指针所指向的对象。 然后呢来调用被攻击对象的FightBack成员函数, 接收被攻击对象的反击,以this指针作为参数。 那CDragon的Hurted成员函数呢就先表现受伤动作 然后再减掉这个LiveValue,如果LiveValue等于0了,就要表现挂掉的这个动作, 那么FightBack久表现反击动作,然后使得被反击对象 受伤。那在多态的写法里面 如果有版本升级, 增加了新的怪物雷鸟,这时候我们肯定要编写CThunderBird这个类, 但是我们不需要往已有的类里面为新的怪物去增加新的Attack和FightBack成员函数了, 不需要增加,也就是说 在这种情况下,原有的类可以原封不动没有压力啊, 很舒服发啊。那么到底是怎么做到这一点的呢? 那我们就来看。 假设我们现在,前面有一堆的这个各种怪物的对象,然后假设我们执行了一条 语句Dragon.Attack( &Wolf),这就是干嘛啊,是让一只龙去攻击一只狼,对不对? 那我们还可以执行Dragon.Attack( &Ghost)这个也没有问题, Dragon里面带个Bird也没有问题,为什么这些类型都能匹配呢? 那我们看因为CDragon.Attack里面那个参数是一个基类的指针, 那我们调用了Attack的时候呢给进去的是 派生类的地址,当然也就是派生类的指针,那我们说过派生类的指针可以赋值给基类的对象 所以在这个地方,参数是基类的指针,但我们调用的时候实参是派生类的对象这个是类型匹配的,没有问题。 那现在就是说我们要做到的就是Dragon.Attack( &Wolf)我们要做到的就是 这条龙所攻击的对象却是得是一只狼, 而这只狼被攻击了它就应该要做出受伤的动作,反击的动作,等等 那怎么做到这一点的呢?我们看到,啊,进到这个Attack成员函数里面 首先要执行p->Hurted,对吧? 那我们注意这个p是基类的指针,这个Hurted是基类和派生类里面都有的 同名虚函数,所以这条语句就是多态,那既然是多态, 也就意味着这个p它要是指向了哪个类的对象,这个Hurted就是哪一个类的Hurted, 那我们执行Dragon.Attack( &Wolf)的时候, 进到了Attack里面来,p就是一个指向Wolf对象的指针, 那这个p->Hurted当然就会调用Wolf对象的Hurted iii一只狼的受伤动作。p->FightBack也是多态, 就会调用狼这个类的FightBack成员函数。 展现狼的反击动作。那如果我们执行Dragon.Attack( &Bird) 进到Attack里面来,由于这时候p指向的是一个CThunderBird类 的对象,所以这里调用的Hurted和FightBack当然就是那个CThunderBird类的 Hurted和FightBack,所以我们就看到, 一有了这些类,我们都不需要动,我们只要新写一个CThunderBird类就可以了。 那新的这个CThunderBird类,就能够和 已有的其他类的那些怪物互相的打来打去 那当然我们这样就节省了很多新增的代码。