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字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成,共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++工资管理系统多继承、虚函数、虚基类的实现方法如下: 1. 多继承继承是指一个类可以从多个基类中继承属性和方法。在C++中,可以通过在类定义中使用逗号分隔多个基类来实现多继承。例如: ```cpp class Base1 { public: void func1(); }; class Base2 { public: void func2(); }; class Derived : public Base1, public Base2 { public: void func3(); }; ``` 在上面的例子中,Derived类从Base1和Base2两个基类中继承了属性和方法。 2. 虚函数 虚函数是指在基类中声明的函数,在派生类中可以被重写。在C++中,可以通过在函数声明前加上virtual关键字来声明虚函数。例如: ```cpp class Base { public: virtual void func(); }; class Derived : public Base { public: void func(); // 重写了Base类中的虚函数 }; ``` 在上面的例子中,Base类中的func()函数是虚函数,Derived类中的func()函数重写了Base类中的虚函数。 3. 虚基类 虚基类是指在多重继承中,为了避免派生类中出现多个基类的副本,而将某个基类设置为虚基类。在C++中,可以通过在基类名前加上virtual关键字来声明虚基类。例如: ```cpp class Base { public: int num; }; class Base1 : virtual public Base { public: void func1(); }; class Base2 : virtual public Base { public: void func2(); }; class Derived : public Base1, public Base2 { public: void func3(); }; ``` 在上面的例子中,Base类被声明为虚基类,Derived类中只包含一个Base类的副本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值