对于大多数程序,似乎是用单个基类的继承就够用了,但是对于一些复杂的模型,可能是用单个基类无法建模:比如孩子可能继承了父亲和母亲两个人的特征。这时就要用到多重继承了。先看一个简单的例子:
class Base1
{
public:
Base1(int i = 1,double d = 1.1, const string s = "word1"):ival1(i),dval1(d),s1(s){cout<<"Base1 constructor"<<endl;}
virtual ~Base1(){cout<<"Base1 deconstructor"<<endl;}
int ival1;
virtual void get()
{
cout<<"Base1 get: dval1 = "<<dval1<<endl;
}
//复制构造函数
Base1(const Base1& b1):ival1(b1.ival1),dval1(b1.dval1),s1(b1.s1)
{
cout<<"Base1 copy constructor"<<endl;
}
Base1& operator=(const Base1& b1)
{
ival1 = b1.ival1;
dval1 = b1.dval1;
s1 = b1.s1;
return *this;
}
protected:
double dval1;
private:
string s1;
};
class Base2
{
public:
Base2(int i = 2,double d = 2.2, const string s = "word2"):ival2(i),dval2(d),s2(s){cout<<"Base2 constructor"<<endl;}
virtual ~Base2(){cout<<"Base2 deconstructor"<<endl;}
int ival2;
virtual void get()
{
cout<<"Base2 get: dval2 = "<<dval2<<endl;
}
//复制构造函数
Base2(const Base2& b2):ival2(b2.ival2),dval2(b2.dval2),s2(b2.s2)
{
cout<<"Base2 copy constructor"<<endl;
}
Base2& operator=(const Base2& b2)
{
ival2 = b2.ival2;
dval2 = b2.dval2;
s2 = b2.s2;
return *this;
}
protected:
double dval2;
private:
string s2;
};
class Derived:public Base1,public Base2
{
public:
//默认构造函数
Derived(){cout<<"Derived constructor"<<endl;}
//接受参数的构造函数
Derived(int i,double d):Base2(i+1,d+1),Base1(i,d){cout<<"Derived constructor"<<endl;}
//析构函数
~Derived(){cout<<"Derived deconstructor"<<endl;}
//复制构造函数
Derived(const Derived &d):derivedval(d.derivedval)
{
cout<<"Derived copy constructor"<<endl;
}
void get()
{
cout<<"Derived get:"<<endl;
cout<<"dval1 = "<<dval1<<endl;
cout<<"dval2 = "<<dval2<<endl;
}
Derived& operator=(const Derived& d)
{
Base1::operator=(d);
Base2::operator=(d);
derivedval = d.derivedval;
return *this;
}
private:
double derivedval;
};
在主函数中:
//多重继承下的成员
Derived d(10,10.1);
cout<<"ival1 = "<<d.ival1<<endl;
cout<<"ival2 = "<<d.ival2<<endl;
d.get();
我们可以看到,多重继承只不过是在类派生列表中指定了更多的基类(以及继承方式)而已。派生类的成员包括所有能从基类继承下来的成员以及自己新增的成员。在这个例子中,派生类继承了Base1的ival1以及dval1,还有get函数。Base2中的也类似。其中ival1与ival2是公有部分,所以在程序中可以直接访问;而dval1与dval2是受保护部分,所以可以通过派生类的get函数函数访问。而基类的私有部分就不能被访问了。
值得注意的是派生类的构造函数:在创建派生类时,先运行自己的构造函数,再调用基类的构造函数,调用顺序顺序与派生类列表中的顺序一致,而与构造函数列表中的无关,这与之前单个基类的不同。而在析构函数中,先调用析构函数析构派生类自己的成员,在依次调用Base2和Base1的析构函数析构基类的成员。
对于一般继承关系中的动态绑定,这里也不例外:
//动态绑定示例:
Base2* bd2 = new Derived();
//调用派生类的get函数
bd2->get();
//调用派生类的析构函数
delete bd2;
Base2* b2 = new Base2();
//调用基类的get函数
b2->get();
//调用派生类的析构函数
delete b2;
对于复制控制函数,这里以赋值操作符为例来说明,在主函数中:
Derived d1(10,10.0);
Derived d2;
d2 = d1;
则在d2 = d1时先调用Derived 的赋值操作符,其中显示调用了两个基类的赋值操作符。
多重继承容易引起一个问题,就是二义性:如果多个基类中有名字相同的成员,那么派生类对象必须指明使用的是哪个基类的成员:
class Father
{
public:
Father(int i = 30,string s = "jack"):age(i),name(s){}
void printname()
{
cout<<"father name: "<<name<<endl;
}
protected:
int age;
string name;
};
class Mother
{
public:
Mother(int i = 29,string s = "rose"):age(i),name(s){}
void printname()
{
cout<<"Mother name: "<<name<<endl;
}
protected:
int age;
string name;
};
//空类:全部依靠继承
class son:public Father,public Mother
{
};
int main()
{
son s;
s.Father::printname();
}
其中,有一种非常隐蔽的二义性问题:
class Base
{
public:
Base(int i = 0,double d = 0.0):ival(i),dval(d){}
double dval;
int ival;
};
class Base2: public Base
{
public:
Base2(int i=0,double j=0,int k=0):Base(i,j),ivalBase2(k){}
int ivalBase2;
};
class Base3:public Base
{
public:
Base3(int i=0,double j=0,int k=0):Base(i,j),ivalBase3(k){}
int ivalBase3;
};
class Drived:public Base2,public Base3
{
public:
Drived(){}
};
int main()
{
Drived d;
//该语句有二义性
d.dval;
return 0;
}
Base中的dval成员在Base1和Base2中都有自己的副本,所以调用时会产生二义性。为了解决这个问题,C++提出了虚继承机制,具体的表述很抽象,用起来很简单,只要把Base3和Base2的声明加上virtual就行了:
class Base2:virtual public Base
class Base3:public virtual Base
这种继承称为虚继承,而Base称为虚基类。此时,从两个虚继承的派生类多重继承下来的派生类,只会对虚基类中的成员保留一份副本。
值得注意的是,在使用虚基类时,类的初始化方式也要发生改变。在没有虚基类时每个类只是初始化自己的成员,并调用基类的构造函数初始化基类。但这样初始化会是得虚基类被多次初始化:比如A派生出B和C,D又多重继承了B和C。则D初始化B时,B会初始化A;而D初始化C时,C也会初始化A。为了解决这个矛盾,在具有虚基类的最底层派生类中,会直接初始化虚基类的成员。然后在调用中间类的构造函数初始化基类成员以外的成员,最后再初始化自己的成员。