虚函数,虚继承

虚函数
多态时实现了接口复用
c++中实现多态有两种,一种是静多态,另一种是动多态
静多态通过重载完成
静多态又叫静态绑定或早绑定,在编译期间确定函数入口地址
动多态通过虚函数完成
动多态又称动态绑定或晚邦定(在运行时动态确定函数入口地址,call的是寄存器)
虚函数关键字virtual
在基类中定义一个虚函数会产生一个虚函数指针,指向一个虚表(虚表在编译期间生成)只有数据段和指令段才能加载到内存,把虚表在数据段中加载在内存中
基类中的函数为虚函数则派生类中同名同参的函数也为虚函数,同时在派生类中虚函数指针会进行合并,从派生类向基类中和并,此时同名同参的函数会发生函数的覆盖关系
什么时候发生动多态
1 通过指针或引用调用虚函数
2.对象要完整
成为虚函数的条件
1,能得到地址
2,依赖对象调用
构造,不可以成为虚函数(构造就是生成对象的)
析构:可以
inline(内联):不可以,无法得到地址(在调用点直接展开)
static(静态函数):不可以(静态函数是属于类的不属于对象,,可以不依赖对象调用)
纯虚函数:定意:virtual void back()=0;
拥有纯虚函数的类为抽象类,不能实例化,只是保留了接口,但是抽象类可以做指针或引用
抽象类做指针或引用的代码如下:实现了接口的复用

# include<iostream>
using namespace std;
#pragma warning(disable:4996)//vs2015不让使用strcpy函数,此行是忽略编译器对strcpy的报错
class animal               //动物类
{
public:
	virtual void animal_call() = 0;//叫声函数
protected:
	char *name;//动物名称
};
class dog:public animal//狗类
{public:
	dog(char *tname)
	{
		int len = strlen(tname);
		name = new char[len + 1]();
		strcpy(name,tname);
	}
	void animal_call()
	{
		cout << name <<" "<<"汪 汪" << endl;
	}
	~dog() {
		delete name;
	}
};
class cat :public animal//猫类
{public:
	cat(char *tname)
	{
		int len = strlen(tname);
		name = new char[len + 1]();
		strcpy(name, tname);
	}
	void animal_call()
	{
		cout << name<<" "<< "喵 喵"<< endl;
	}
	~cat() {
		delete name;
	}
};
void is_animal_call(animal &rsh)//发出叫声,基类的指针或引用派生类对象,整个过程中发生了动多态,派生类中同名同参的叫声函数在虚表中替换了基类中的叫声虚函数,发生了函数的覆盖
{
	rsh.animal_call();
}
int main()
{
	dog my_dog("dog");
	cat my_cat("cat");
	is_animal_call(my_dog);
	is_animal_call(my_cat);
}

因为虚指针在类中的优先级最高,所以他在派生类的起始位置,但虚函数指针式从派生类往基类中合并,所以当使用基类指针指向派生类对象时指针指向的是派生类对象起始处
但存在一个问题,如果基类中无虚函数,但在派生类中有虚函数,当使用基类的指针指向派生类对象时指针并没有指向派生类的起始位置,当使用基类指针释放派生类对象时因为没有指向派生类对象的起始位置,释放会存在问题,而解决这种问题的方法是把基类的析构函数变成虚析构,使基类中存在虚函数指针。
虚继承
在继承中除了单继承,还有多继承
在多继承中有一种继承为菱形继承:
**菱形继承**
则派生类D中的内存结构为:
D的内存结构
从图中可以看出派生类D中基类A含有两个,分别在D的两个基类中,这样会造成二义性,和内存浪费,所以此处可以进程虚继承。
虚继承的关键字和虚函数的关键字一直都为:“virtual”,是哪个类出现了问题,就在继承哪个类时加入virtual,如上例所示,D中是A类出现问题所以在B和C继承A时加入virtual,
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B,public C
{};
虚继承可以解决以上问题,他的解决方法是把基类A放在当前作用域的最下方,此时在类中生成一个虚基类指针指向虚基类表(存放偏移量)通过偏移量找到A类数据
虚继承D的内存结构

当vbptr处于同级是他们不会合并,但vbptr不处于同级时会进行合并
eg:
class B:virtual public A
{};
class C:virtual public A
{};
class D:virtual public B,public C
{};

D的内存结构
原本类B在类D中会有一个指向B的vbptr但因为类C中有一个类A的vbptr所以两个vbptr发生了合并,按照向上合并规则,所以合并到了C中的vbptr中 此时C中vbptr指向两个类 C:中vbptr内容
在这里插入图片描述
B中vbptr内容
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43440004/article/details/85806635