zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++ 继承与多态详解2

C++继承 详解 多态
2023-09-14 09:15:35 时间

Question00:
静态绑定/静多态:
编译时期

动态绑定/动多态:
调用函数最终被 编译成 call eax。寄存器的值在运行时才知道,eax从虚函数表vftable里拿的虚函数地址。
Question01:
1 很多类定义的对象,用一个变量去统一的接收:基类的指针或者引用。
2 想知道基类指针p 指向的哪个Derive对象?要借助一个识别RTTI的类型强转,dynamic_cast 当基类指针真的指向一个派生类对象时,强转才会成功。若失败,返回的指针q是NULL

Base *p;
Derive *q=dynamic_cast<Derive*>(p);

##########################分割线####################################
C++ 多重继承 虚继承 虚基类

Question1: 多重继承易出现:菱形继承的结构

在这里插入图片描述
注:打开VS的命令行提示 :查看 类的内存布局
命令如下:cl 文件名.cpp /d1reportSingleClassLayout类名
在这里插入图片描述

class Base // 虚基类
{
public:
	Base(int data = 10) :ma(data) { cout << "Base()" << endl; }
	~Base() { cout << "~Base()" << endl; }
private:
	int ma;
};
/*
vbptr(virtual base ptr) ------>  vbtable  0  8
mb
ma
*/

class Derive : public Base // 普通继承
{
public:
	Derive(int data = 20) :Base(data), mb(data) { cout << "Derive()" << endl; }
	~Derive() { cout << "~Derive()" << endl; }
private:
	int mb;
};
int main()
{
	cout << sizeof(Derive) << endl;                              //  8
	return 0;
}

普通继承,派生类内存布局如下:
在这里插入图片描述
虚继承,派生类内存布局如下: 大小变成12 而不是8
在这里插入图片描述
分析: Base是虚基类,把虚基类部分的数据移动到 派生类数据部分的后面。并在原来放虚基类部分的数据的地方 加上一个vbptr :虚基类指针。指向虚基类表 vbtable。这个vbtable有两行:
第一行:vbptr离 最近的作用域 起始位置的偏移量。但它就在最起始位置 故偏移量为0字节
第二行:虚基类指针vbptr到虚基类数据ma的偏移量 8字节
其代码如下:

class Base // 虚基类
{
public:
	Base(int data = 10) :ma(data) { cout << "Base()" << endl; }
	~Base() { cout << "~Base()" << endl; }
private:
	int ma;
};
/*
vbptr(virtual base ptr) ------>  vbtable  0  8
mb
ma
*/

class Derive : virtual public Base // 虚继承
{
public:
	Derive(int data = 20) :Base(data), mb(data) { cout << "Derive()" << endl; }
	~Derive() { cout << "~Derive()" << endl; }
private:
	int mb;
};
int main()
{
	cout << sizeof(Derive) << endl;
	return 0;
}

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
下面给出一个菱形继承:

class A
{
public:
	A(int data) :ma(data) { cout << "A()" << endl; }
	~A(){cout << "~A()" << endl; }
private:
	int ma;
};
class B :  public A 
{
public:
	B(int data) :A(data), mb(data) { cout << "B()" << endl; }
	~B(){cout << "~B()" << endl; }
private:
	int mb;
};
class C :  public A
{
public:
	C(int data) :A(data), mc(data) { cout << "C()" << endl; }
	~C(){cout << "~C()" << endl; }
private:
	int mc;
};

class D : public B, public C
{
public:
	D(int data):B(data), C(data), md(data) { cout << "D()" << endl; }
	~D(){cout << "~D()" << endl; }
private:
	int md;
};

int main()
{
	D d(10);
	
	return 0;
}

其构造 析构顺序如图:
在这里插入图片描述
此时D的内存布局如下:
在这里插入图片描述
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
解决方式:谁直接继承A,就把继承方式改为:虚继承
即:

class B : virtual public A 
class C:  virtual public A 

此时的运行结果:
在这里插入图片描述
此时的内存布局:
在这里插入图片描述
分析:
1 现在B虚继承A,需要把(A的作用域:ma)移动到派生类D的最后面去,即 紧挨着md;而且在原来虚基类部分放vbptr。
2 现在C虚继承A,需要把(A的作用域:ma)移动到派生类D的最后面去,但是发现后面已经有一个A了,所以就不放了;而且在原来虚基类部分放vbptr,偏移量。
3 内存布局如下:
B::
vbptr========》 vbtable 0 14h 20字节
mb
C::
vbptr========》 vbtable 0 0Ch 12字节
mc
md

A::
ma
4: 通过虚继承解决了两个ma的问题;但是随之而来的是:1 内存变大 2 原来在D的作用域里面需要D构造的是 B和C,原来的两个A构造隶属于B C,但现在把虚基类数据搬到D作用域的最下面了。
出现问题如下:
在这里插入图片描述
这里D为什么要操心 A的构造?因为虚继承以后,派生类内存布局更改,A和B C一样也是成为D应该负责构造的基类。所以这里必须显式地指定出来虚基类A的构造

class D : public B, public C
{
public:
	D(int data):B(data), C(data), A(data),md(data) { cout << "D()" << endl; }
	~D(){cout << "~D()" << endl; }
private:
	int md;
};

一个派生类有两种基类:普通基类和虚基类。虚基类数据的构造优先级高,在这里生成一个派生类D的对象,先调用A的构造 接下来B C D。且A只构造一份。
在这里插入图片描述
如何解决派生类里面拥有间接基类多份数据(尤其是这种菱形继承中):应该在所有从基类直接继承的地方都采用 虚继承。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这里 A B C D E

class A
{
public:
	A(int data) :ma(data) { cout << "A()" << endl; }
	~A(){cout << "~A()" << endl; }
private:
	int ma;
};
class B : virtual public A 
{
public:
	B(int data) :A(data), mb(data) { cout << "B()" << endl; }
	~B(){cout << "~B()" << endl; }
private:
	int mb;
};
class C : virtual public A
{
public:
	C(int data) :A(data), mc(data) { cout << "C()" << endl; }
	~C(){cout << "~C()" << endl; }
private:
	int mc;
};
class E
{
public:
	E(int data):me(data) { cout << "E()" << endl; }
	~E(){ cout << "~E()" << endl; }
private:
	int me;
};
class D : public B, public C,virtual public E
{
public:
	D(int data):B(data), C(data),E(data), A(data),md(data) { cout << "D()" << endl; }
	~D(){cout << "~D()" << endl; }
private:
	int md;
};

int main()
{
	D d(10);
	
	return 0;
}

运行结果:
在这里插入图片描述
派生类D的内存布局:
B::
vbptr========》 vbtable 0 14h 20字节
mb
C::
vbptr========》 vbtable 0 0Ch 12字节
mc
md

A::
ma
E::
me
在这里插入图片描述
第一个vbtable 变成3行:因为派生类虚基类 E里面的东西,并没有给E产生vbptr,而是合并到B的虚表里面了。按照原来的做法:应该在md上面,mc下面产生一个vbptr,指向me的偏移量 12字节,但是基类里面有vbptr,所以派生类就不用产生了,把内容和第一个表合并,故而在第一个表里面放me的偏移量24字节。

如果是下面的情况:

class D : virtual public E,public B, public C { public: D(int data):B(data), C(data),E(data), A(data),md(data) { cout << "D()" << endl; } ~D(){cout << "~D()" << endl; } private: int md; };
运行结果:
在这里插入图片描述
内存布局:
在这里插入图片描述
这个指针合并:派生类自己产生vbptr,将它合并到基类继承来的第一个vbptr里,以节省内存。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Question02:

class Base// Base是基类
{
public:
	Base(int a) :ma(a){ cout << "Base()" << endl; }
	virtual void show() { cout << "Base::show()" << endl; }//虚函数
protected:
	int ma;
};
class Derive :virtual public Base
{
public:
	Derive(int data) :Base(data), mb(data){ cout << "Derive()" << endl; }
	void show() { cout << "Derive::show()" << endl; }
private:
	int mb;
};
int main()
{
	Base*p = new Derive(10);
	p->show();
	delete p;

	return 0;
}

在这里插入图片描述
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Question03:
常量迭代器 普通迭代器
正向迭代器 反向迭代器