虚基类的概念
一般,不希望在一个派生类中存在某个公共基类的多个同名的成员变量。虽然也可以通过在成员变量名前面加上“类名::”消除其二义性,但解决这个问题的最好办法是使用虚基类。虚基类方法可以保证在任何一个存在公共基类的派生类中,不会存在一个以上的同名成员变量。
所谓虚基类,就是说一个类层次中,如果某个派生类存在一个公共基类,将这个基类设置为虚基类,这时从不同的路径继承过来的该类成员在内存中只保留一份拷贝。因此,虚基类方法可以消除成员变量的二义性。
注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。声明虚基类的一般形式为:
class 派生类名:virtual 继承方式 基类名
即在声明派生类时,将关键字virtual加到相应的继承方式前面,经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是基类成员只保留一次。
需要注意:为了保证虚基类在派生类中继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多继承。
例:虚基类的使用。
#include <iostream.h>
class A
{
public:
A()
{
a = 7;
cout << "A class a=" << a << endl;
}
protected:
int a;
};
class B: virtual public A
{
public:
B()
{
a = a + 10;
cout << "B class a=" << a << endl;
}
};
class C : virtual public A
{
public:
C()
{
a = a + 20;
cout << "C class a=" << a << endl;
}
};
class D : public C, public B
{
public:
D()
{
cout << "D class a=" << a << endl;
}
};
void main()
{
D obj;
}
虚基类的初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。要注意以下几点:
① 如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。例如:
class A //定义基类
{ A(int i) {} //基类构造函数,有一个参数
...};
class B : virtual public A // A作为B的虚基类
{ B(int n) : A(n) {} // B类构造函数,在初始化表中对虚基类初始化
...};
class C : virtual public A //A作为C的虚基类
{ C(int n) : A(n) {} //C类构造函数,在初始化表中对虚基类初始化
...};
class D : public B, public C
{ D(int n) : A(n), B(n), C(n) {} //类D的构造函数,注意对虚基类A也要做初始化
...};
② 建立一个对象时,如果这个对象中包含从虚基类继承来的成员,则虚基类的成员由最后派生类的构造函数,通过调用虚基类的构造函数进行初始化。该派生类的其他基类对虚基类构造函数的调用都自动忽略。
如果用上例中的类D创建一个对象,则构造函数的执行顺序是A()→B()→C()→D()。
③ 若同一层次中包含虚基类和非虚基类,应该先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数。例如:
class x : public y, virtual public z
{ //...
};
x obj;
定义类x的对象obj后,构造函数的执行顺序是在在z()→y()→x()。
④ 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
⑤ 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下。
⑥ 若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。例如:
class A { A() {...}
...};
class B: public A
{ B() {...}
...};
class C1: virtual public B
{ C1(){...}
...};
class C2 : virtual public B
{ C2() {...}
...};
class D : public C1, public C2
{ D() {...}
...};
D d;
定义类D的对象d后,构造函数执行的顺序是A()→B()→C1()→C2()→D()。
虚基类应用举例
例:声明一个人Person类作为后面派生类的虚基类,由Person类分别派生出教师Teacher类和学生Student类,最后由Teacher类和Student类共同派生出研究生Graduate类。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string str1, char s, int a)
{
name = str1;
sex = s;
age = a;
}
protected:
string name;
char sex;
int age;
};
class Teacher : virtual public Person
{
public:
Teacher(string str1, char s, int a, string t) : Person(str1, s, a)
{
title = t;
}
protected:
string title;
};
class Student : virtual public Person
{
public:
Student(string str1, char s, int a, float f_s) : Person(str1, s, a)
{
score = f_s;
}
protected:
float score;
};
class Graduate : public Teacher, public Student
{
public:
Graduate(string str1, char s, int a, string t, float f_s, float f_w):
Teacher(str1, s, a, t), Student(str1, s, a, f_s), Person(str1, s, a), wage(f_w){}
void show()
{
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "title:" << title << endl;
cout << "score:" << score << endl;
cout << "wage:" << wage << endl;
}
private:
float wage;
};
int main()
{
Graduate g1("Zhang shan", 'f' , 22, "assistant", 83, 1080.78);
g1.show();
return 0;
}
执行结果:
name:Zhang shan
sex:f
age:22
title:assistant
score:83
wage:1080.78
许多专业人士认为:不要提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多重继承,能用单继承解决的问题不要使用多重继承。也是由于这个原因,有些面向对象的程序设计语言(Java,Delphi)并不支持多重继承。