C++的三大特性:封装,继承,多态。继承的目的是就是为了代码重用,避免的重复代码的编写。继承分为单继承和多继承,单继承就是每次继承的基类只能有一个,属于一对一的关系;多继承则是子类可以同时继承自多个基类,拥有多个基类的特性,属于一对多的关系!
虚继承的定义
在多继承中派生类不能多次直接继承同一个基类,但是派生类的直接基类可能派生自同一个基类。例如:定义一个雇员类Employee作为顶层基类;另外定义一个管理人员类Manager和一个销售人员类Salesman作为Employee类的直接派生类 。再用Manager类和Salesman类共同派生出销售经理类SalesManager。
在由Employee派生出的Manager类的对象中,存在Employee类的子对象;同时Salesman也有一个Employee类的子对象。这样在最终的SalesManager中就存在两份Employee对象的拷贝。这样对SalesManager操作时就很容易产生二义性,如:
SalesManager wang; wang.setName("王某");此时编译时,编译器将会报错,setName函数存在二义性,此时编译器不知道到底该调用Manager的setName还是Salesman的setName方法。虽然可以用指定域名的方法解决编译错误,但是这样并不能达到我们想要的目的,因为通过指定域名的方式将会导致同一个SalesManager可能名字既是王某优势李某等!
此时虚继承就出现了,虚继承就可以解决此类问题。在C++中,在定义公共基类的派生类的时候,如果在继承方式前使用关键字virtual对继承方式限定,这样的继承方式就是虚拟继承,公共的基类成为虚基类。这样,在具有公共基类的、使用了虚拟继承方式的多个派生类的公共派生类中,该基类的成员就只有一份拷贝。虚继承的定义形式如下:
class 派生类名:virtual 继承方式1 派生类1[[,virtual]继承方式2 派生类2,...]{派生类成员声明与定义};
下面给出不使用虚继承和使用了虚继承时,上面SalesManager类的使用示例:
不使用虚继承:
#include <iostream> #include <cstdlib> using namespace std; class Employee { public: Employee(char* ename) { strcpy(name, ename); cout << "Employee:" << name << endl; } char* getName(){ return name; } void setName(char* ename) { strcpy(name, ename); cout << "set Employee name:" << name << endl; }; private: char name[20]; }; class Salesman:public Employee { public: Salesman(char* ename) :Employee(ename) { cout << "Salesman:" << ename << endl; } }; class Manager :public Employee { public: Manager(char* ename) :Employee(ename) { cout << "Manager:" << ename << endl; } }; class SalesManager :public Salesman, public Manager { public: SalesManager(char* ename) :Salesman(ename), Manager(ename) { cout << "SalesManager:" << ename << endl; } }; int main() { SalesManager wang("wang"); //wang.setName("kang"); //语句1
<span style="white-space:pre"> </span>//wang.Salesman::setName("kang"); //语句2 system("pause"); return 0; }
运行结果:
很明显在SalesManager中有两份Employee的拷贝,并被初始化了两次!
注意:注释掉的语句1不能正确执行,将会产生二义性!语句2可以正确执行
使用了虚继承的示例:只需要在Salesman类和Manager类的继承方式前加上virtual关键字,并在SalesManager类的初始化列表中添加Employee的初始化方式。
#include <iostream> #include <cstdlib> using namespace std; class Employee { public: Employee(char* ename) { strcpy(name, ename); cout << "Employee:" << name << endl; } char* getName(){ return name; } void setName(char* ename) { strcpy(name, ename); cout << "set Employee name:" << name << endl; }; private: char name[20]; }; class Salesman :virtual public Employee { public: Salesman(char* ename) :Employee(ename) { cout << "Salesman:" << ename << endl; } }; class Manager :virtual public Employee { public: Manager(char* ename) :Employee(ename) { cout << "Manager:" << ename << endl; } }; class SalesManager :public Salesman, public Manager { public: SalesManager(char* ename) :Salesman(ename), Manager(ename), Employee(ename) { cout << "SalesManager:" << ename << endl; } }; int main() { SalesManager wang("wang"); //wang.setName("kang"); system("pause"); return 0; }
执行结果:
此时,可以看到SalesManager中只有一份Employee的拷贝了!
注意:
在虚拟继承方式下,构造函数的调用次序跟非虚拟继承不同,其执行次序遵循下面几条规则:
(1)虚基类的构造函数最先调用,然后才是非虚基类的构造函数;
(2)如果类的同一继承层次上包含多个虚基类,则他们的构造函数调用顺序跟继承的先后次序一致。如果某个虚基类的构造函数在前面被调用了,则不会再次调用;
(3)如果虚基类继承自非虚的类,则先调用虚基类的基类构造函数,在调用虚基类的构造函数。