栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【虚基类、虚函数及应用】

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【虚基类、虚函数及应用】

虚基类

1.虚基类存在的意义

当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。
在继承中产生歧义的原因有可能是继承类继承了基类多次,如概述图所示,子类C最后会接受分别来自A和B的同一个或多个相同拷贝,从而产生了多个拷贝,即不止一次的通过多个路径继承类在内存中创建了基类成员的多份拷贝。而这些是A和B从父类继承而来,所以C类该继承A还是B传下来的还是都接受呢?
2.虚基类的定义方式:

虚基类(virtual base class)定义方式如下:
class 派生类名:virtual 访问限定符 基类类名{…};
class 派生类名:访问限定符 virtual 基类类名{…};

不用虚基类时产生的数据冗余和二义性问题:
如下代码属于菱形继承方式:

下面是不用虚基类处理此继承关系时的代码:

class person
{

public:
	string _pname;
	string _sex;
	person(){cout << "Create person " << endl;
	}
	person(string& pname ):_pname(pname){
		cout << "Create person " << endl;
	}
	virtual ~person() { cout << "Destroy person" << endl; }
		 void fun()
		{
			cout << "person::fun()" << endl;
		}
};
class student  :public   person
{
public:
	string _id;
	student() { cout << "Create student" << endl; }
	student(string &sname,string &id) : _id(id)
	{
		person::_sex = "男";
		cout << "Create student" << endl;

	}
	 ~student() { cout << "Destroy student" << endl; }
	 virtual void fun()
	 {
		 cout << "student::fun" << endl;
	 }
};
class employee :public   person
{
protected:
	string _ename;
public:
	employee(string name):_ename(name){
		person::_sex = "女";
		cout << "Create employee" << endl;
	}
	~employee() { cout << "Destroy employee" << endl; }
};
class estudent :public student,public employee 
{
protected:
	string _esname;
public:
	estudent(string esname,string &id):student(esname,id),_esname(esname){

		cout << "Create estudent" << endl;
	}
	~estudent() {}
	virtual void fun()
	{
		cout << "estudent::fun" << endl;
	}
};

int main()
{
	string esname{ "李华" };
	string sex{ "女" };
	string id{ "202201" };
	person s;
	estudent es1(esname,  id);
	student es2(esname,  id);
	es1._sex = { "女" };//error
	es2._sex= { "女" };//ok !

	return 0;
}

我们可以发现想调动estudent类的成员时,编译器不知道你到底想调用哪个类中的person 属性。

我们访问人类型中的_sex 属性,如果不明确是哪个类中的_sex则无法编译通过。如果我们只想要一个person类的实例,那就要用到虚基类。

虚基类的处理代码:

class person
{

public:
	string _pname;
	string _sex;
	person(){cout << "Create person " << endl;_sex={"男"};
	}
	person(string& pname ):_pname(pname){
		cout << "Create person " << endl;
	}
	virtual ~person() { cout << "Destroy person" << endl; }
		 void fun()
		{
			cout << "person::fun()" << endl;
		}
};
class student  :public  virtual person
{
public:
	string _id;
	student() { cout << "Create student" << endl; }
	student(string &sname,string &id) : _id(id)
	{
		cout << "Create student" << endl;

	}
	 ~student() { cout << "Destroy student" << endl; }
	 virtual void fun()
	 {
		 cout << "student::fun" << endl;
	 }
};
class employee :public virtual  person
{
protected:
	string _ename;
public:
	employee(string name):_ename(name){
		cout << "Create employee" << endl;
	}
	~employee() { cout << "Destroy employee" << endl; }
};
class estudent :public student,public employee 
{
protected:
	string _esname;
public:
	estudent(string esname,string &id):student(esname,id),employee(esname),_esname(esname){

		cout << "Create estudent" << endl;
	}
	~estudent() {}
	virtual void fun()
	{
		cout << "estudent::fun" << endl;
		cout<<"person::_sex"<
	string esname{ "李华" };
	string id{ "202201" };
	person s;
	estudent es1(esname,  id);
	esl._sex={"中性"};
	es1.fun();
	return 0;

注意,一旦使用虚基类,那么派生类中构造函数中的基类的属性就不用传参了,因为没有必要。

编译器不同,虚表指针指向的处理方式就会不同。


可以看到 person 只有单独一份。


接下来是一个测试,看能不能成功打印_sex;

int main()
{
	string esname{ "李华" };
	string id{ "202201" };
	//person s;
	estudent es1(esname,  id);
	person*ps=&es1;
	cout<_sex< 


结果是可以打印的,也就是说会指针自动偏移到person类。

静态转化和动态转换

int main()
{
person s;
	//cout<(ps);//如果没有虚基类,则此静态转换可以成功。
	}

虚析构函数
在进行指针指向对象的操作时对象的析构有时会出现内存的泄漏:
1)虚构函数前没有加vitual 关键字的情况:

class person
{

public:
	string _pname;
	string _sex;
	 ~person() { cout << "Destroy person" << endl; }
};
class student  :public   person
{
public:
	string _id;
	 ~student() { cout << "Destroy student" << endl; }
	 }
};

class estudent :public student,public employee 
{
protected:
	string _esname;
public:
	~estudent() {}
};
int main(){
string esname{ "李华" };
	string sex{ "女" };
	string id{ "202201" };
	student* s1 = new estudent();
	
	delete s1;
	}


可以看到泄漏了estudent对像。

2)虚构函数前加了vitual 关键字的情况:

class person
{
public:
	virtual ~person() { cout << "Destroy person" << endl; }
};

class estudent :public student,public employee 
{
public:
	~estudent() {}
};


在虚析构函数析构时会查虚表。

运行时的多态

类型名+ 点的形式属于编译时的多态。


运行时的多态:

总结:运行时的多态性: 公有继承 + 虚函数 + (指针或引用调用虚函数)。

int main()
{
person s;
	estudent es1(esname, id);
	person* ps = &es1;
	}

多态的原理

虚函数表的示例:运行时多态的原理

虚函数指针表简称虚表, 虚表就是虚函数指针的集合,虚函数指针表本质是一个存储虚函数指针的指针数组,这个数组的首元素之上存储RTTI(运行时类型识别信息的指针),从数组下标0开始依次存储虚函数地址, 最后面放了一个nullptr。

虚函数指针表存储在只读数据段(.rodata)

静态联编和动态联编:

静态联编(static binding)早期绑定: 静态联编是指在编译和链接阶段,就将函数实现和函数调用关联起来。

动态联编(dynamic binding)亦称滞后联编(late binding)或晚期绑定: 动态联编是指在程序执行的时候才将函数实现和函数调用关联起来。
C++语言中,使用类类型的引用或指针调用虚函数(成员选择符“->”),则程序在运行时选择虚函数的过程,称为动态联编

关于动态联编的几个例子

1.当派生类的对象属性不是公有,属性值有初始值的情况:

class object
{
public:
	virtual void fun(int a = 10)const
	{
		cout << "object ::fun::a :" << a << endl;
	}
};
class base :public object
{
private:
	virtual void fun(int x = 20)const
	{
		cout << "base::fun  x: " << x << endl;
	}
};
int main()
{
	base base1;
	object* op =&base1;
	op->fun();
	return 0;
}


先看流程

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1022647.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号