C++继承总结(下)——菱形继承

一.什么是菱形继承

菱形继承是多继承的一种特殊情况,一个类有多个父类,这些父类又有相同的父类或者祖先类,那么该类就会有多份重复的成员,从而造成调用二义性和数据冗余。

cpp 复制代码
class Person
{

public:
	Person()
	{
		cout << "Person构造" << endl;
	}
public:
	int _name = 0;
	int _age = 0;
};



class Student :  public Person
{
	
public:
	Student()
	{
		cout << "Student构造" << endl;
	}
	int _stuid = 0;
};

class Teacher :  public Person
{
public:
	Teacher()
	{
		cout << "Teacher构造" << endl;
	}
	int _jobid = 0;
};

class Assistant : public Student, public Teacher
{
public:
	Assistant()
	{
		cout << "Assistant构造" << endl;
	}
	int _task = 0;
};
int main()
{
	Assistant a;
	//a._name;//二义性:访问Student的_name还是Teacher的_name呢?
    
    //需要指定类域访问
	a.Student::_name = 1;
	a.Student::_age = 2;
	a._stuid = 3;
	a.Teacher::_name = 4;
	a.Teacher::_age = 5;
	a._jobid = 6;
	a._task = 7;
	
	return 0;
}

从a的内存布局可以看到,a中有两份_name和_age,它们是从Student和Teacher类继承下来的。二义性的问题可以通过指定类域访问解决,但数据冗余的问题是无法规避的,必须引入新的技术------虚继承

二.虚继承的用法

只需在继承那个祖先类时加上关键字virtual即可

cpp 复制代码
class Person
{

public:
	Person()
	{
		cout << "Person构造" << endl;
	}
public:
	int _name = 0;
	int _age = 0;
};

class Student :  virtual public Person
{
	
public:
	Student()
	{
		cout << "Student构造" << endl;
	}
	int _stuid = 0;
};

class Teacher :  virtual public Person
{
public:
	Teacher()
	{
		cout << "Teacher构造" << endl;
	}
	int _jobid = 0;
};

class Assistant : public Student, public Teacher
{
public:
	Assistant()
	{
		cout << "Assistant构造" << endl;
	}
	int _task = 0;
};
int main()
{
	Assistant a;

	a.Student::_name = 1;
	a.Student::_age = 2;
	a._stuid = 3;
	a.Teacher::_name = 4;
	a.Teacher::_age = 5;
	a._jobid = 6;
	a._task = 7;
	
	return 0;
}

虚继承前:

虚继承后:

可以看到,Person构造函数只调用了一次。

再来看看虚继承后a的内存分布:

虚继承后,重复的那部分成员被单独拎了出来,只有一份,此时就不存在二义性的问题了。a.Student::_name;a.Student::_name;a._name访问的是同一份数据。同时也解决了数据冗余的问题。

三.虚继承的原理

Student和Teacher中多出的这两个东西是什么呢?这似乎是一个地址,那我们在内存中看一看(注意是小端存储,低字节存低位数据,高字节存高位数据,故地址应该为007e9b4c和007e9b54)

注意这是16进制,故第一个数 是20,第二个数是12。

在看看上面的内存分布,会发现:006ff8d0这个地址加上20,006ff8d8加上12,刚好是006ff8e4,也就是重复的Person那部分变量的起始地址。

Assistant对象中将Person放到的了对象组成的最下面,这个Person同时属于Student和Teacher,给Student和Teacher都添加一个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存了偏移量,通过偏移量可以找到下面的Person。事实上,虚基表中存放了两个数据,第二个数是偏移量,第一个数与多态中的虚表有关,这里不作展开,后面的多态会讲到。

四.例题

1.多继承中指针偏移问题?下面说法正确的是( )

cpp 复制代码
class Base1 {  public:  int _b1; };
class Base2 {  public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
     Derive d;
     Base1* p1 = &d;
     Base2* p2 = &d;
     Derive* p3 = &d;
     return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

d的内存分布如图,p1和p3相等,不同之处在于p3解引用能访问整个对象,p1解引用只能访问Base1哪部分

2.下面程序输出结果是什么? ()

cpp 复制代码
using namespace std;
class A{
public:
     A(const char *s) { cout<<s<<endl; }
     ~A(){}
};
class B:virtual public A
{
public:
     B(const char *s1,const char*s2):A(s1) { cout<<s2<<endl; }
};
class C:virtual public A
{
public:
     C(const char *s1,const char*s2):A(s1) { cout<<s2<<endl; }
};
class D:public B,public C
{
public:
     D(const char *s1,char *s2,const char *s3,const char *s4)
        :B(s1,s2)
        ,C(s1,s3)
        ,A(s1)
     { cout<<s4<<endl;}
};
int main() {
     D *p=new D("class A","class B","class C","class D");
     delete p;
     return 0;
}

A:class A class B class C class D

B:class D class B class C class A

C:class D class C class B class A

D:class A class C class B class D

首先明确一点,A的构造函数会调一次还是三次?因为虚继承解决数据冗余的问题,A的成员在D中只有一份,故只会调用一次构造函数。那么调用时机在哪呢?应该是在调用B,C的构造函数之前,A构造完后,构造B,C时就不会再去调用A的构造函数了。而B,C的构造函数谁先调用?答案是按继承顺序来,B先继承,故B先调用。故答案为A

假如把两个virtual去掉,A的构造函数会调用几次?答案是编译错误,去掉virtual后这就是一个普通的多继承,B,C中都有A,D不能单独去初始化A。去掉初始化列表中的A(s1)就正确了,A会被构造两次。

相关推荐
IT技术分享社区19 分钟前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
极客代码22 分钟前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow
疯一样的码农29 分钟前
Python 正则表达式(RegEx)
开发语言·python·正则表达式
&岁月不待人&1 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
StayInLove1 小时前
G1垃圾回收器日志详解
java·开发语言
无尽的大道1 小时前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒1 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~1 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio1 小时前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE1 小时前
【Java SE】StringBuffer
java·开发语言