IT狗工作室C语言C语言&嵌入式

第8篇:C++ 类多继承(中篇)

2019-11-08  本文已影响0人  铁甲万能狗

前一篇介绍了C++继承的特征,我们本篇将介绍C++继承的分类

class 子类名称 :继承模式 父类名1,继承模式 父类名2,.....{
      //类代码体
} 

为了深入浅出,本篇拿我以前玩过的《最终幻想X-2》为例子,如果你玩过RPG游戏的话,你会知道下面的例子是多么地贴切适合本篇的主题.

但需要说明的是,这是仍然不太好的设计形式。我这里仅仅是用来说明C++多继承的特性。然而它不太符合现实中复杂的需求,我们本文最后会给出更符合复杂需求的方案。

一个角色,可以装配两个职业晶球-黑魔法师和白魔法师,那么角色Role就继承了上面BlackMagic类和WhiteMagic类的所有方法了。

ss17.png
多继承的示例
这是上面UML类图的代码实现
#include <string>
#include <iostream>

using namespace std;

class BlackMagic{
public:
  BlackMagic(){cout<<"初始化-黑魔法师"<<endl;}
  ~BlackMagic(){cout<<"内存回收-黑魔法师"<<endl;}
  size_t fira(){
    cout<<"fire初级火魔法"<<endl;
  }

  size_t thurder(){
    cout<<"初级雷魔法"<<endl;
  }

  size_t bio(){
    cout<<"毒属性魔法"<<endl;
  }
};


class WhiteMagic{
public:
  WhiteMagic(){cout<<"初始化-白魔法师"<<endl;}
  ~WhiteMagic(){cout<<"内存回收-白魔法师"<<endl;}
  size_t haste(){
    cout<<"haste魔法"<<endl;
  }

  size_t holy(){
    cout<<"圣属性魔法"<<endl;
  }

  size_t might(){
    cout<<"青魔法"<<endl;
  }
};

class Role:public WhiteMagic,public BlackMagic{
  string d_name;
  size_t d_hp;
  
public:
    Role(const string& name,
         size_t hp):d_name(name),d_hp(hp){
        cout<<"角色初始化 -"<<d_name
        <<"HP:"<<d_hp<<endl;
    }
    ~Role(){cout<<"内存回收-游戏角色"<<endl;}
    
    size_t attck(){
        cout<<d_name<<"施加attack方法"<<endl;
    }
    
    size_t magic(){
        cout<<d_name<<"施加magic方法"<<endl;
    }
};

int main(void){
    Role yuna=Role("Yuna",1500);
    
    yuna.fira();
    yuna.holy();
    return 0;
}

多继承的RAII约定

首先我们,从下面的输出结果可以看出子类在初始化过程

垃圾回收的过程即会继承列表中定义的父类顺序相反。

谨慎使用多继承

如果在实际项目中,你能够想到什么不足之处吗?如果你了解《最终幻想X-2》这个游戏的职业系统设定有些了解的,你体会到上面的代码上下文组织太过死板缺乏灵活性。FFX-2光是职业设定就有10多个,如果一个Role类要继承10个职业设定的其中2个,那么需要编写多少次上面示例代码组合?


你要编写继承组合需要那么多个囧.....

显然多继承无法适用于那些变数类型太大的情况,因此在你使用多继承进行组织代码之前,请在心中考虑这些问题。

如果你自信心十足的话~恭喜你,小弟想拜你为师。

另外C++的多继承另外一个😈邪恶的一面,就是棱形问题,考虑如下这个UML类图存在什么问题?


ss_17.png

当一个类的两个父类具有共同的基类时,就会发生"棱形问题"。 例如,在上图的继承链中,Role类到Ability类,它获取了多少个属性副本?这是一个不合理的设计,根据上面分析的多继承RAII约定
Role类的分类初始化流程会如下顺序依次执行初始化:

  1. Ability() ➔Magic() ➔BlackMagic()
  2. Ability()➔Magic() ➔WhiteMagic()
  3. Ability()➔Physical()➔Warrior()

因此整个多继承链中Magic的构造函数执行了2次,Ability的构造函数执行了3次,因此Role构造整个过程中,获得了2份Magic实例中的属性副本,3份Ability实例中的属性副本。

这对于计算机资源管理角度考虑这是不可取的。所以对多继承没有深入认识的话,会对你的代码组织会造成各种负面的效果。

我们模拟一下上面分析过程的步骤1和步骤2上面,只需修改上文的示例代码,但注意这个是一个设计不良的示例。

class Ability{
public:
     Ability(){cout<<"初始化 - Ability实例"<<endl;}
     ~Ability(){cout<<"内存释放 -Ability实例"<<endl;}
};

class Magic:public Ability{
public:
     Magic(){cout<<"初始化 - Magic实例"<<endl;}
     ~Magic(){cout<<"内存释放 - Magic实例"<<endl;}
};

class WhiteMagic:public Magic{
   ....
};

class BlackMagic:public Magic{
  ....
};

最后输出的程序结果,跟我们以上分析的结果一样

多继承的可选方案

多继承就我个人而言,在局部的上下文中应用还是可以的,如果大规模使用的话,我更倾向于用其他流行的设计模式作为优选方案。而且在打算动手敲代码之前,不妨想本文示例中,用UML类图和工程流程图理清了你的思路之后再动手组织代码。上面的示例代码,我们可以使用常见的组合模式来组织代码,比如N个职业设定,我们都单继承于一个Career的基类,并且我们向Role类的构造函数传递2个Career类,这种组织代码的模式更为灵活,能够适用绝大多数场合。

class Career{...};

class BlackMagic: public Career{...};
....
class WhiteMagic:public Career{...};
....

class Role{
public:
     Role(Career ca1,Career ca2,string name,...)
     {...}
};

后记

我们最后一篇会提到多继承中,子类如何调用父类指定构造函数的问题

上一篇 下一篇

猜你喜欢

热点阅读