1. C++允许一个派生类从多个基类继承,这种继承方式称为多重继承,当从多个基类继承时每个基类之间用逗号隔开,比如class A:public B, public C{}就表示派生类A从基类B和C继承而来。
2. 多重继承的构造函数和析构函数:多重继承中初始化的次序是按继承的次序来调用构造函数的而不是按初始化列表的次序, 比如有class A:public B, public C{}那么在定义类A的对象A m时将首先由类A的构造函数调用类B的构造函数初始化B,然后调用类C的构造函数初始化C,最后再初始化对象A,这与在类A中的初始化列表次序无关。
3. 多重继承中的二义性问题:
3.1. 成员名重复:比如类A从类B和C继承而来,而类B和C中都包含有一个名字为f的成员函数,这时派生类
A创建一个对象,比如A m; 语句m.f()将调用类B中的f函数呢还是类C中的f函数呢?
3.2. 多个基类副本:比如类C和B都从类D继承而来,这时class A:public B, public C{}类A从类C和类B同时继
承而来,这时类A中就有两个类D的副本,一个是由类B继承而来的,一个是由类C继承而来的,当类A的
对象比如A m;要访问类D中的成员函数f时,语句m.f()就会出现二义性,这个f函数是调用的类B继承来的
f还是访问类C继承来的函数f呢。
3.3.在第2种情况下还有种情况,语句classA:public B,public C{},因为类A首先使用类B的构造函数调用共同基类D的构造函数构造第一个类D的副本,然后再使用类C的构造函数调用共同基类D的构造函数构造第二个类D的副本。类A的对象m总共有两个共享基类D的副本,这时如果类D中有一个公共成员变量d,则语句
m.B::d和 m.D::d都是访问的同一变量,类B和类D都共享同一个副本,既如果有语句m.D::d=3 则 m.B::d也
将是3。这时m.C::d的值不受影响而是原来的值。为什么会这样呢?因为类A的对象m总共只有两个类D的副本,所以类A的对象m就会从A继承来的两个直接基类B和C中,把从共同基类D 中最先构造的第一个副本作为类A的副本, 即类B构造的D的副本。 因为class A:public B,public C{}最先使用B的构造函数调用共
同基类类D创造D的第一个副本,所以类B和类D共享同一个副本。
3.4. 解决方法:对于第1 和第 2 种情况都可以使用作用域运算符::来限定要访问的类名来解决二义性。但对于第二种情况一般不允许出现两个基类的副本,这时可以使用虚基类来解决这个问题 ,一旦定义了虚基类,就只会有一个基类的副本 。
例:多重继承及其二义性
class A {
public:
int a;
A(int i){
a=i;cout<<"A"<<endl;
}
}; //共同的基类A
class B:public A {
public:
int b;
B():A(4){
cout<<"B"<<endl;
}
};
class C:public A{
public:
int c;
C():A(5){
cout<<"C"<<endl;
}
};
class D:public B,public C
{
public:
int d;
D():C(),B(){
cout<<"D"<<endl;
}
}; //先调用类B的构造函数而不会先调用类C的构造函数,初始化的顺序与初始化列表顺序无关
int main()
{
D m; //输出ABACD,调用构造函数的顺序为类ABACD,注意这里构造了两个类A的副本,调用了两次类A的构造函数
// m.a=1; //错误,出现二义性语句,因为类D的对象m有两个公共基类A的副本,这里不知道是调用由类B继承来的A的副本还是由类C继承来的A的副本
m.B::a=1;cout<<m.B::a; //输出1。
m.A::a=3; cout<<m.B::a<<m.A::a;//输出33,类B和类A共享相同的副本,调用类A中的a和类B继承来的a是一样的,因此最后a的值为3。
m.C::a=2; cout<<m.C::a;//输出2,类C和类A类B的副本是彼此独立的两个副本,因此,这里不会改变类B和类A的副本中的a的值。
}
虚基类:方法是使用virtual关见字,比如class B:public virtual D{},class C:virtual public D{}注意关见字virtual的次
序不关紧要。类B和类C以虚基类的方式从类D继承,这样的话从类B和类C同时继承的类时就会只创见一个类D的副本,比如class A:public B, public C{}这时类A的对象就只会有一个类D的副本,类A类B类C类D四个类都共享一个类D的副本,比如类D 有一个公有成员变量d,则m.d和 m.A::d,m.B::d,m.C::d,m.D::d 都将访问的
是同一个变量。这样类A的对象调用类D中的成员时就不会出 现二义性了 。
虚基类的构造函数:比如class B:public virtual D{};class C:virtual public D{}; class A:public B,public C{};这时当创
建类A的对象A m时初始化虚基类D将会使用类A的构造函数直接调用虚基类的构造函数初始化虚基类部分,而
不会使用类B或者类C的构造函数调用虚基类的构造函数初始化虚基类部分, 这样就保证了只有一个虚基类的副本。
但是当创建一个类B和类C的对象时仍然会使用类B和类C中的构造函数调用虚基类的构造函数初始化虚基类。
例:虚基类及其构造函数
class A {
public:
int a;
A(){
cout<<"moA"<<endl;
}
A(int i){
a=i;cout<<"A"<<endl;
}
};
class B:public virtual A {
public:
int b;
B(int i):A(4){
cout<<"B"<<endl;
}
}; //以虚基类的方式继承
class C:public virtual A {
public:
int c;
C():A(5){
cout<<"C"<<endl;
}
};
class D:public B, public C {
public:
int d;
D():A(4),C(),B(2){
cout<<"D"<<endl;
}
};
//因为类D是虚基类,所以类D会直接调用虚基类的构告函数构造虚基类部分,而不会使用类B或者类C的构造函数来调用虚基类的构造函数初
始化虚基类部分。要调用虚基类中的带参数的构造函数必须在这里显示调用,如果不显示调用就将调用虚基类的默认构造函数。
int main()
{
D m; //输出ABCD,注意这里没有重复的基类副本A。
C m1; //输出AC,虽然D是虚基类,但当创建类C的对象时仍然会使用类C的构造函数调用虚基类的构造函数初始化虚基类部分。
cout<<m.a<<endl; //输出4,因为使用了虚基类所以这里就不存在二义性问题。
m.B::a=1;
cout<<m.a<<m.A::a<<m.B::a<<m.C::a<<endl; //输出1111,类A,类B,类C,类D共享的是同一个虚基类的副本,所以这里输出四个1。
}
2. 多重继承的构造函数和析构函数:多重继承中初始化的次序是按继承的次序来调用构造函数的而不是按初始化列表的次序, 比如有class A:public B, public C{}那么在定义类A的对象A m时将首先由类A的构造函数调用类B的构造函数初始化B,然后调用类C的构造函数初始化C,最后再初始化对象A,这与在类A中的初始化列表次序无关。
3. 多重继承中的二义性问题:
3.1. 成员名重复:比如类A从类B和C继承而来,而类B和C中都包含有一个名字为f的成员函数,这时派生类
A创建一个对象,比如A m; 语句m.f()将调用类B中的f函数呢还是类C中的f函数呢?
3.2. 多个基类副本:比如类C和B都从类D继承而来,这时class A:public B, public C{}类A从类C和类B同时继
承而来,这时类A中就有两个类D的副本,一个是由类B继承而来的,一个是由类C继承而来的,当类A的
对象比如A m;要访问类D中的成员函数f时,语句m.f()就会出现二义性,这个f函数是调用的类B继承来的
f还是访问类C继承来的函数f呢。
3.3.在第2种情况下还有种情况,语句classA:public B,public C{},因为类A首先使用类B的构造函数调用共同基类D的构造函数构造第一个类D的副本,然后再使用类C的构造函数调用共同基类D的构造函数构造第二个类D的副本。类A的对象m总共有两个共享基类D的副本,这时如果类D中有一个公共成员变量d,则语句
m.B::d和 m.D::d都是访问的同一变量,类B和类D都共享同一个副本,既如果有语句m.D::d=3 则 m.B::d也
将是3。这时m.C::d的值不受影响而是原来的值。为什么会这样呢?因为类A的对象m总共只有两个类D的副本,所以类A的对象m就会从A继承来的两个直接基类B和C中,把从共同基类D 中最先构造的第一个副本作为类A的副本, 即类B构造的D的副本。 因为class A:public B,public C{}最先使用B的构造函数调用共
同基类类D创造D的第一个副本,所以类B和类D共享同一个副本。
3.4. 解决方法:对于第1 和第 2 种情况都可以使用作用域运算符::来限定要访问的类名来解决二义性。但对于第二种情况一般不允许出现两个基类的副本,这时可以使用虚基类来解决这个问题 ,一旦定义了虚基类,就只会有一个基类的副本 。
例:多重继承及其二义性
class A {
public:
int a;
A(int i){
a=i;cout<<"A"<<endl;
}
}; //共同的基类A
class B:public A {
public:
int b;
B():A(4){
cout<<"B"<<endl;
}
};
class C:public A{
public:
int c;
C():A(5){
cout<<"C"<<endl;
}
};
class D:public B,public C
{
public:
int d;
D():C(),B(){
cout<<"D"<<endl;
}
}; //先调用类B的构造函数而不会先调用类C的构造函数,初始化的顺序与初始化列表顺序无关
int main()
{
D m; //输出ABACD,调用构造函数的顺序为类ABACD,注意这里构造了两个类A的副本,调用了两次类A的构造函数
// m.a=1; //错误,出现二义性语句,因为类D的对象m有两个公共基类A的副本,这里不知道是调用由类B继承来的A的副本还是由类C继承来的A的副本
m.B::a=1;cout<<m.B::a; //输出1。
m.A::a=3; cout<<m.B::a<<m.A::a;//输出33,类B和类A共享相同的副本,调用类A中的a和类B继承来的a是一样的,因此最后a的值为3。
m.C::a=2; cout<<m.C::a;//输出2,类C和类A类B的副本是彼此独立的两个副本,因此,这里不会改变类B和类A的副本中的a的值。
}
虚基类:方法是使用virtual关见字,比如class B:public virtual D{},class C:virtual public D{}注意关见字virtual的次
序不关紧要。类B和类C以虚基类的方式从类D继承,这样的话从类B和类C同时继承的类时就会只创见一个类D的副本,比如class A:public B, public C{}这时类A的对象就只会有一个类D的副本,类A类B类C类D四个类都共享一个类D的副本,比如类D 有一个公有成员变量d,则m.d和 m.A::d,m.B::d,m.C::d,m.D::d 都将访问的
是同一个变量。这样类A的对象调用类D中的成员时就不会出 现二义性了 。
虚基类的构造函数:比如class B:public virtual D{};class C:virtual public D{}; class A:public B,public C{};这时当创
建类A的对象A m时初始化虚基类D将会使用类A的构造函数直接调用虚基类的构造函数初始化虚基类部分,而
不会使用类B或者类C的构造函数调用虚基类的构造函数初始化虚基类部分, 这样就保证了只有一个虚基类的副本。
但是当创建一个类B和类C的对象时仍然会使用类B和类C中的构造函数调用虚基类的构造函数初始化虚基类。
例:虚基类及其构造函数
class A {
public:
int a;
A(){
cout<<"moA"<<endl;
}
A(int i){
a=i;cout<<"A"<<endl;
}
};
class B:public virtual A {
public:
int b;
B(int i):A(4){
cout<<"B"<<endl;
}
}; //以虚基类的方式继承
class C:public virtual A {
public:
int c;
C():A(5){
cout<<"C"<<endl;
}
};
class D:public B, public C {
public:
int d;
D():A(4),C(),B(2){
cout<<"D"<<endl;
}
};
//因为类D是虚基类,所以类D会直接调用虚基类的构告函数构造虚基类部分,而不会使用类B或者类C的构造函数来调用虚基类的构造函数初
始化虚基类部分。要调用虚基类中的带参数的构造函数必须在这里显示调用,如果不显示调用就将调用虚基类的默认构造函数。
int main()
{
D m; //输出ABCD,注意这里没有重复的基类副本A。
C m1; //输出AC,虽然D是虚基类,但当创建类C的对象时仍然会使用类C的构造函数调用虚基类的构造函数初始化虚基类部分。
cout<<m.a<<endl; //输出4,因为使用了虚基类所以这里就不存在二义性问题。
m.B::a=1;
cout<<m.a<<m.A::a<<m.B::a<<m.C::a<<endl; //输出1111,类A,类B,类C,类D共享的是同一个虚基类的副本,所以这里输出四个1。
}