IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> C++(23)——理解多重继承(菱形继承、半圆形继承)、虚基类和虚继承 -> 正文阅读

[数据结构与算法]C++(23)——理解多重继承(菱形继承、半圆形继承)、虚基类和虚继承

多重继承

概念: 一个派生类如果只继承一个基类,称作单继承,那么如果继承了多个基类,就称作多继承。
比如:

class C:public A,public B{};

请添加图片描述

多重继承的优点:

多重继承可以做更多的代码复用!
派生类通过多重继承,可以得到多个基类的数据和方法,更大程度的实现了代码复用

多重继承有优点,那就也会存在缺陷:

首先我们通过菱形继承了解一下多重继承的缺陷:

菱形继承

菱形继承是多继承的一种:
如下图所示:请添加图片描述
图中的继承关系会产生什么样的问题呢?
我们通过如下的代码示例理解一下:

class A
{
public:
	A(int data) :ma(data) { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
protected:
	int ma;
};
class B :public A
{
public:
	B(int data) :A(data), mb(data) { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
protected:
	int mb;
};
class C :public A
{
public:
	C(int data) :A(data), mc(data) { cout << "C()" << endl; }
	~C() { cout << "~C()" << endl; }
protected:
	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; }
protected:
	int md;
};
int main()
{
	D d(10);

	return 0;
}

运行结果如下:
在这里插入图片描述

对于基类A而言,构造了两次,析构了两次!
并且,通过分析各个派生类的内存布局我们可以看到:请添加图片描述
于是我们得出结论:
如果多继承的数量增加,那么派生类中重复的数据也会增加!

半圆形继承

除了菱形继承外,还有其他多重继承的情况,也会出现响应的问题:
请添加图片描述

解决多重继承

通过分析我们知道了,多重继承的主要问题是,通过多重继承,有可能得到重复的基类数据,并且可能重复的构造和析构同一个基类对象
那么如何能够避免重复现象的产生呢?
答:虚基类

虚基类

对于如下的这个示例而言,B虚继承了A,所以把A称作虚基类

class A
{
private:
	int ma;
};
class B : virtual public A
{
private:
	int mb;
};

注意区分:抽象类是指有纯虚函数的类,而虚基类是指被虚继承的类。

未使用虚继承方式之前,对于类B实例化出的一个对象而言,其内存结构大致如下:
在这里插入图片描述
在使用了虚继承方式之后,其内存结构发生了如下的变化,变成12个字节:
在这里插入图片描述
大致了解了上述这样一个过程,我们来探究一下这个将虚函数和虚继承结合会出现什么问题?

class A
{
public:
	virtual void fun(){cout<<"call A::fun"<<endl;}
private:
	int ma;
};
class B:virtual public A
{
public:
	void fun(){cout<<"call B::fun"<<endl;}
private:
	int mb;
};

int main()
{
	A *p = new B();
	p->fun();
	delete p;
	return 0;
}

在这里插入图片描述

为什么delete会出现这种问题呢?

完成B对象的构建,那么依照我们之前了解到的内存分布规则后,我们指针p开辟的内存应该是指向vbptr还是vfptr呢?
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址。
即便是虚继承方式,在对象B的内存结构中,它的成员被挪动到最下方并在最上方添加一个虚基类指针后,p指向的还是基类的首地址。
所以在进行delete时,并没有完全释放干净所有内存。
(注意这里是堆区空间,如果是栈空间,出作用域后系统会自动回收也不会报错)
在这里插入图片描述

解决之前的问题

了解了虚基类是什么,那么虚基类又是如何解决多重继承的问题的呢?
改成虚继承,A就变成了虚基类

class A
{
public:
	A(int data) :ma(data) { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
protected:
	int ma;
};
class B :virtual public A
{
public:
	B(int data) :A(data), mb(data) { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
protected:
	int mb;
};
class C :virtual public A
{
public:
	C(int data) :A(data), mc(data) { cout << "C()" << endl; }
	~C() { cout << "~C()" << endl; }
protected:
	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; }
protected:
	int md;
};

我们做出修改:
将B和C的继承方式都改为虚继承;接下来继续运行代码:
此时会报错:
请添加图片描述
提示说:“A::A” : 没有合适的默认构造函数可用;
为什么会这样呢?

是因为:

  1. 刚开始B和C单继承A的时候,实例化对象时,会首先调用基类A的构造函数,,到了D,由于多继承了B和C,所以在实例化D的对象时,会首先调用B和C的构造函数,然后调用自己(D)的。
  2. 但是这样会出现A重复构造的问题,所以,采用虚继承,把有关重复的基类A改为虚基类,这样的话,对于A构造的任务就落到了最终派生类D的头上,但是我们的代码中,对于D的构造函数:
    D(int data) : B(data), C(data), md(data) { cout << “D()” << endl;}
    并没有对A进行构造。
  3. 所以会报错。 那么我们就给D的构造函数,调用A的构造函数: D(int data) :A(data), B(data), C(data), md(data) { cout << “D()” << endl; }

想想我们之前的内存结构改变的方式:
虚继承后,挪动B和C的成员变量至内存结构的最下方且添加了vbptr后,发现A::ma挪动不需要两份,因此只挪过去一份,由此其内存结构如下:
**加粗样式**

说两句题外话
查看对象的内存结构的命令:

cl 源文件名称 /d1reportSingleClassLayout类名称

《剑指offer》经典题目

class A{};
sizeof(A) = 1;

class B:public A{};
sizeof(B) = 1;

class A
{
	virtual void fun(){};
};
class B:public A{};
sizeof(B) = 4;//vftable

class A
{
	virtual void fun(){};
};
class B:virtual public A{};
sizeof(B) = 8;//vftable+vbptr

总之,虚继承可以解决多重继承的问题,虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成,共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-16 22:43:30  更:2022-03-16 22:45:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:46:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码