第九章 多重继承
9.1分类化继承
1.多重继承:一个对象可以有两个或更多不同的父类,并可以继承每个父类的数据和行为。
派生的分类对每个父类仍然符合"是一个"规则或"作为一个"关系。
同时扮演多个角色
magnitude number
char integer complex
3.多重继承中存在的问题
(1)父类存在同名的方法。
例如:CardDeck 中存在函数draw()画一个Card
GraphicalObject中存在函数draw()画一个image
那么他们的子类GraphicalCardDeck 应该怎么画呢?
解决方案一:使用全限定名
GraphicalCardDeck gcd;
Card *aCard=gcd->CardDeck::draw();
gcd->GraphicalObject::draw();
不够理想:
因为语法上与其他的函数调用语法不同,程序员必须记住那个方法是来自与那个类。
解决方案二:使用重定义和重命名的结合
class GraphicalCardDeck:public CardDeck,public GraphicalObject{
public:
virtual Card *draw(){
return CardDeck::draw();
}
virtual void draw(Graphics *g){
GraphicalObject::draw(g);
}
}
GraphicalCardDeck gcd;
Graphics g;
gcd->draw();
gcd->draw(g);
使用重定义仅解决了单独使用GraphicalCardDeck类时的部分问题。还会产生新的问题,如下。
GraphicalObject *g = new GraphicalCardDeck();
g->draw();//使用的方法是CardDeck中的draw方法,显然不正确。
解决方案三:
思路:在C++语言中,解决这一问题的典型方法就是引入两个新的辅助类。并且使用不同的方法名称来重定义draw操作。
class CardDeckParent:public CardDeck{
public:
virtual void draw(){CardDeckDraw();}
virtual void cardDeckDraw(){CardDeck::draw();}
};
Class GraphicalObjectParent:public GraphicalObject{
public:
virtual void draw(){goDraw();}
virtual void goDraw(){GraphicalObject::draw();}
};
class GraphicalCardDeck:public CardDeckParent,GraphicalObjectParent{
public:
virtual void cardDeckDraw(){}
virtual void goDraw(){}
};
(1)子类继承这些新的父类,改写相应的新方法。
(2)当独立使用子类时,新的子类对两个行为都可以访问
(3)当以替换的方式对该对象赋值给任何一个父类的实例时,都会产生所希望的行为。//先去找子类,如果子类中有方法那么就调用,如果子类中没有方法就去父类中找。
9.2接口的多重继承
- java,c#都不支持类的多重继承,但他们都支持接口的多重继承
- 对于子类来说,接口不为其提供任何代码,所以不会产生两部分继承代码之间的冲突
interface CardDeck{
public void draw()
}
interface GraphicalObject{
public void draw()
}
class GraphicalCardDeck implements CardDeck,GraphicalObject{
public void draw(){...}//only one method
}
注意:如果是不同类型的签名,那么在最后的实现中必须实现接口中的所有方法。
9.2.2实现多重继承的另一种方法
内部类,可以避免很多语义的问题。
class GraphicalCardDeck extends CardDeck{
public void draw(){
//外部类改写CardDeck方法
}
private drawingclass drawer= new drawingClass();
public GraphicalObject myDrawingObject(){return drawer;}
private calss drawingClass extends GraphicalObject{
public void draw(){
//内部类改写GraphicalObject方法
}
}
}
新问题:这样解决之后GraphicalCardDeck类的实例可以赋值给类型为CardDeck的变量,但是却不可以赋值给类型为GraphicalObject的变量。
9.3继承于公共祖先
9.3.1菱形继承
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJ4tvBhp-1588945687140)(C:\Users\60917\AppData\Roaming\Typora\typora-user-images\1588683021336.png)]
- 存在于菱形继承中的问题是子类应该拥有公共基类数据成员的一份还是两份拷贝?
例子一:
class Link{
public:
Limk *nextLink;
}
class CardDeck:public Link{...};
class GraphicalObject:public Link{...};
- 当创建同时继承自CardDeck类和GraphicalObject类的子类时,这个子类应该包含多少个nextLink字段?
- 假设纸牌列表和图形对象列表是不同的,那么每种类型的列表都应该有各自的链接。因此,子类用于两个独立的链接字段看起来更为恰当。
例子二:
输入输出流即是输入流的派生,也是输出流的派生。
但是,只存在一个对象,两个文件指针引用相同的数值。即只需要公共祖先的一份拷贝。 - 在c++语言中,这个问题通过在父类列表中使用virtual修饰符来解决。
- 关键字virtual意味着在当前派生类中,超类可以多次出现,但是只包含超类的一份拷贝。
class Stream{
File *fid
};
class InStream:public virtual Stream{
int open(File *)
};
class OutStream:public virtual Stream{
int open(File *)
};
class InOutStream:public InStream,public OutStream{
int open(File *)
};
9.4构造函数与析构函数
9.4.1构造函数和析构函数的调用顺序
- 构造函数也遵循先祖先(基类),再客人(成员对象),后自己(派生类)的原则,有多个基类之间则严格按照派生定义是从左到右的顺序来排列先后。析构函数调用则正好相反。
9.4.2虚基类的初始化
- 虚基类的构造函数在非虚基类之前调用
- 如果同一层次中包含多个虚基类,这些虚基类的构造函数按其说明次序调用。
9.4.1构造函数和析构函数的调用顺序
- 构造函数也遵循先祖先(基类),再客人(成员对象),后自己(派生类)的原则,有多个基类之间则严格按照派生定义是从左到右的顺序来排列先后。析构函数调用则正好相反。
9.4.2虚基类的初始化
- 虚基类的构造函数在非虚基类之前调用
- 如果同一层次中包含多个虚基类,这些虚基类的构造函数按其说明次序调用。
- 若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类构造函数