这两个概念在C++的书里面似乎也很少提及,所以看到这两个名词的时候我也往往自动忽略,今天有时间上网百度了一下,找到三篇写得不错的文章:
《详解多重继承》
多重继承很好理解,后两篇是讲虚拟继承的,前一篇写的比较浅,看完基本能够理解这个概念,后一篇写的很详细,读起来不太好理解,自己动手写了代码在VC上跑了一下。
总的来说就是:
- 多重继承其实就是相当于合并了多个基类的成员(当然还有自身扩展的成员),与单继承的继承方式基本一致,要注意的是构造函数的调用顺序以及避免两个基类中成员(包括函数名)重名的情况发生,即使两个基类的成员函数有不同的参数类型也不行。
- 虚拟继承是为了避免多重继承的两个以上基类来自同一个(二级)基类,因为这样一来多重继承的派生类就有了多套同样的基类成员,既浪费了内存,又产生多义性。所以引入虚拟的概念,类似于虚拟函数表一样,在虚拟继承的派生类中用一个指针指向同一个基类内存。
- 为什么不将全部的继承设为虚拟继承?因为虚拟继承会影响效率(间接引用基类),在非多重继承情况下占用了空间(引入指针)。
- 多重继承和虚拟继承一般不推荐使用。
难点:
- 第三篇文章中关于特殊的初始化语义这部分看得我有点乱,所以写了下面的代码:
class base { public : base(string name):base_name(name) { cout<<"in base"<<endl; } string base_name; }; class A:virtual public base { public: A(string A_name,string base_name):A_name(A_name),base(base_name) { cout<<"in A"<<endl; } string A_name; }; class B:virtual public base { public: B(string name, string name2):B_name(name),base(name2) { cout<<"in B"<<endl; } string B_name; }; class C:public A, public B { public://对所有基类都初始化 C(string name1, string name2,string name3,string name4) :base(name1),A(name2,name2),B(name3,name3),C_name(name4) { cout<<"in C"<<endl; } string C_name; }; bool isNumber(char * str); int _tmain(int argc, _TCHAR* argv[]) { C c("1","2","3","4"); cout<<c.base_name.c_str()<<"," <<c.A_name.c_str()<<"," <<c.B_name.c_str()<<"," <<c.C_name.c_str(); }
输出结果是:
in base
in A
in B
in C
1, 2, 3, 4
说明base的构造函数只调用了一次,而且是在C类构造函数中被调用的,而A和B中的base()部分都失效了,所以其实传给A和B的构造函数中的name2是多余的,这也就是文章中说的写两个构造函数的原因。
class Bear : public virtual ZooAnimal {
public:
// 当作为最终派生类时
Bear( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, "Bear" ),_dance( two_left_feet )
{}
// ... rest the same
protected:
// 当作为一个中间派生类时
Bear() : _dance( two_left_feet ) {}
// ... rest the same
};
2.第二篇文章中那道笔试题。
一开始没想明白,看了后面的评论才豁然开朗,主要是要记得虚拟继承情况下并非只有一个指针,还要给基类也开辟一段内存,所以比非虚拟继承的情况下多了一个指针。另外虚拟函数的表指针也不再与基类公用,需要另外多出一个虚拟函数表指针。
最后,如果你想继续挑战脑力,可以看这篇:《C++多继承和虚继承的内存布局》
看完会对整体内存有更深的理解。