资讯详情

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

虚基类

继承中歧义的原因可能是继承类继承了多次基类,如概述图所示,子类C最终将分别接受A和B同一或多个相同的副本产生多个副本,即基本成员的多个副本在内存中创建不止一次。这些是A和B从父类继承,那么C类应该继承A还是B继承还是接受?

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

以下代码属于菱形继承方式: blog.csdnimg.cn/ded0abe6d56444a2b8c57862639a9a33.png) 下面是不用处理此继承关系的代码:

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 属性。

虚基类的处理代码:

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"<<person::_sex<<endl;
	}
};

int main()
{ 
        
	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<<ps->_sex<<endl;
	return 0;

结果是可以打印的,也就是说会指针自动偏移到person类。但是这个结果放在不同的编译器可能会不一样,有的可能不会自动偏移。

int main()
{ 
        
person s;
	//cout<<s._sex << endl;
	estudent es1(esname, id);
	string s1;
	person* ps = &es1;

	estudent* pest = static_cast<estudent*>(ps);//如果没有虚基类,则此静态转换可以成功。
	}

一个重要的作用:重置虚表指针(析构时设置虚表指针指向自己类型的虚表)

class object
{ 
        
	int value;
public:
	object(int x=0):value(x){ 
         add(); }
	~object(){ 
         add(); }
	virtual void add(int x = 10) { 
         cout << "object ::add x " << x << endl; }

	
};
class base :public object
{ 
        
public:
	int num;
	
public:
	base(int x = 0) :object(x + 10), num(x) { 
         add(100); }
	~base() { 
         add(200); }
	virtual void add(int x )
	{ 
        
		cout << "base::add x: " << x << endl;
	}
};
int main()
{ 
        
	base base;
	
	return 0;
}

可以看到 ,在构造函数和析构函数中调动的函数,查虚表与否的结果都是一样的。编译器在优化时选择不再查虚表。

在obj的构造函数中调动add函数前已经完成了虚表和虚表指针的设置,obj的构造函数中调动add函数时,将会调动的是它自己的虚表。(虚表指针的指向的是obj的虚表)


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对像。

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(object *const this,int a = 10)const
	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;
	base1.fun()//base(&base1);
	op->fun();
	return 0;
}

先看流程 我们把下图右侧的es1对象当作base1对象,当拿obj类型的指针指向base1对象时,op就指向了此对象的首地址,。所以就算op是obj类型,调动的fun()函数也是base的虚表。

示例2:

class Object { 
        
	int value;
public:
	Object(int x = 0) :value(x) { 
        }
	void print() { 
         cout << "object::print<<" << endl; add(10); }
	virtual void add(int x) { 
         cout << "object::add :x" << x << endl; }

};
class Base :public Object
{ 
        
	int num;
public:
	Base(int x = 0) :Object(x + 10), num(x) { 
        }
	void show()
	{ 
        
		cout << "Base ::show" << endl;
		print();
	}
	virtual void add(int x) { 
         cout << "base ::Add x" << x << endl; }
};
int main()
{ 
        
	Base base;
	base.show();//show(&base);
}

流程如图: 思考:

int main()
{ 
        
	Base base;
	Object &ob = base;
	ob.print(); 是什么结果?
	//Object ob = base;
	//ob.print(); 又是什么结果?
}

Object &ob = base; 以引用的方式调动虚函数将会查引用于Base的虚表。

Object ob = base; 以值传递,调动的obj的虚表。

示例3:memset 与虚表指针

class object
{ 
        

	int val;
	int data[10];
public:
	object() { 
         memset(this, 0, sizeof(object)); }
	virtual void fun()
	{ 
        
		cout << "fun " << endl;
	}
};
	int main()
	{ 
        
		object obj;
		object* op = &obj;
		obj.fun();
		op->fun();
	}

memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。 当使用memset初始化obj对象时,对象内存中的虚表指针连同整个空间全部被置为空。所以此时用此函数时比较危险的。

示例4 :this 指针与 虚表指针

using namespace std;
class object
{ 
        

	int val;
public:
	object(int x=0):val(x) { 
         }
	void show() { 
         cout << "object::show "  << endl; }
	void print()
	{ 
        
		cout << "object::val " << val << endl;
	}
};
	int main()
	{ 
        
	object *op=nullptr;
		op->show();
		op->print();
	}

打印结果??


class object
{ 
        

	int val;
public:
	object(int x=0):val(x) { 
         }
	void show() { 
         cout << "object::show "  << endl; }
	void print()
	{ 
        
		cout << "object::val " << hex<<val << endl;
	}
};
int main()
	{ 
        
	object* op = (object*)malloc(sizeof(object));
		op->show();
		op->print();
	}

打印结果? 编译器认为op指向了一个对象,op指向了val,但这个对象没有初始化,所以是个随机值、(cdcdcdcdc是未初始化的数据值)。

class object
{ 
        

	int val;
public:
	object(int x=0):val(x) { 
         }
	void show() { 
         cout << "object::show "  << endl; }
	virtual void print()
	{ 
        
		cout << "object::val " << hex<<val << endl;
	}
};
int main()
	{ 
        
	object* op = (object*)malloc(sizeof(object)
        标签: 贴片恢复二极管es1jsod

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司