C++虚继承与虚基类

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)如果虚基类继承自非虚的类,则先调用虚基类的基类构造函数,在调用虚基类的构造函数。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页