C++多继承,菱形继承,虚继承 ,虚基表和虚基表指针

多继承

当一个子类只有一个直接父类时,称这个继承关系为单继承

当一个子类有两个或两个以上的直接父类时,称这个继承关系为多继承

当存在多继承时,就有可能形成这样一种继承关系

我们称上面的继承关系为菱形继承,来看看菱形继承如何实现

 
class Person
{
public:
 Person(const string name = "") :_name(name)
  {}
//private:
//注意这里为了方便调试,暂且将成员变量声明为公有的
 string _name;
};
class Student :public Person
{
public:
 Student(const string name = "", const string stunum = "") :Person(name), _stunum(stunum)
 {}
private:
 string _stunum;
};
class Teacher :public Person
{
public:
 Teacher(const string name = "", const string worknum = "") :Person(name), _worknum(worknum)
 {}
private:
 string _worknum;
};
class Assistant :public Student,public Teacher
{
public:
 Assistant(const string name = "", const string stunum = "", const string worknum = "", const string majorCourse = "")
  :Student(name, stunum), Teacher(name, worknum), _majorCourse(majorCourse)
 {}
private:
 string _majorCourse;
};
int main()
{
 Assistant a;
 a._name = "小明";
 system("pause");
 return 0;
}

这样的菱形继承,Assistant类中会有两份name, (会造成数据冗余) 当想修改name的值时,并不知道是要访问那个name(存在二义性)

若想解决二义性,我们可以指定类域来进行访问,如下面代码:

但是这里仍旧是有两个name,并没有解决数据冗余的问题

 

 

 

这里就有一种虚继承,关键字:virtual

注意:这里的虚继承是在Person的下一层,采用虚继承,Student类虚继承了Person类,Teacher类虚继承了person类

class Person
{
public:
	Person(const string  name = "") :_name(name)
	{}
//private://注意这里为了方便调试,暂且将成员变量声明为私有的
	string _name;
};
class Student :virtual public Person
{
public:
	Student(const string  name = "", const string stunum = "") :Person(name), _stunum(stunum)
	{}
private:
	string _stunum;
};
class Teacher :virtual public Person
{public:
	Teacher(const string  name = "", const string  worknum = "") :Person(name), _worknum(worknum)
	{}
private:
	string _worknum;
};
class Assistant :public Student,public Teacher
{
public:
	Assistant(const string name = "", const string stunum = "", const string worknum = "", const string majorCourse = "")
		:Student(name, stunum), Teacher(name, worknum), _majorCourse(majorCourse)
	{}
private:
	string _majorCourse;
};

int main()
{
	Assistant a;
	a.Student::_name = "小明";

	system("pause");
	return 0;
}

这里的name是只保存了一份

 

我们来用一个简单的菱形继承来看一下虚继承是如何来解决二义性和数据冗余的

看以下代码: 

 

class A
{
public:
	int _a;
};
class B : public A
{
public:
	int _b;
};
class C :  public A
{
public:
	int _c;
};
class D :public B, public C
{
public:
	int _d;
};

int main()
{
	D d1;
	cout << sizeof(D) << endl;
	system("pause");
	return 0;
}

 

当没有采用虚继承时D类的大小为20,这里不难理解,因为D中有两个a

 

class A
{
public:
	int _a;
};
class B :virtual  public A
{
public:
	int _b;
};
class C : virtual public A
{
public:
	int _c;
};
class D :public B, public C
{
public:
	int _d;
};

int main()
{
	D d1;
	cout << sizeof(D) << endl;
	system("pause");
	return 0;
}

 

我们看到这里的D类的大小为24,可是我们上面说,如果采用虚继承的话,D中就只有一个a,那么我们期待的D的大小就为16,可是这里是24,看上去也并没有解决数据冗余的问题

我们这里在通过内存来看这里问题

当我们没有定义虚继承时:

class A
{
public:
	int _a;
};
class B :  public A
{
public:
	int _b;
};
class C :  public A
{
public:
	int _c;
};
class D :public B, public C
{
public:
	int _d;
};

int main()
{
	D d1;
	cout << sizeof(D) << endl;

	d1.B::_a = 1;
	d1.C::_a = 2;
	d1.B::_b = 3;
	d1.C::_c = 4;
	d1.D::_d = 5;
	system("pause");
	return 0;
}

 

来看看内存中是如何来存放这些数的

当没有进行虚继承时发现,这里是很好理解的,B和C中都会有一个A

 

当我们采用虚继承时:

class A
{
public:
int _a;
};
class B :virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
class D :public B, public C
{
public:int _d;
};
int main()
{
D d1;
cout << sizeof(D) << endl;
d1.B::_a = 1;
d1.C::_a = 2;
d1.B::_b = 3;
d1.C::_c = 4;
d1.D::_d = 5;
system("pause");
return 0;
}

 

 

发现A类是被放在D中的最下面,而且B和C 中除了放自己的数据成员,还有一个地址,还有B和C是如何来找到A的,至于为什么B和C 要来找到A呢,当我们想将一个D类对象赋值给B类对象时,这时候就要发生我们上面介绍的切片行为,这时候B和C就要来找到A

 

很明显这里的B和C中只是多存了一个指针,当A中的成员比较多的话,就达到了就解决数据冗余的目的

那么B类和C类的大小分别为多少?

看下面的代码:

class A
{
public:
	int _a;
};
class B : virtual public A
{
public:
	int _b;
};
class C :virtual  public A
{
public:
	int _c;
};
class D :public B, public C
{
public:
	int _d;
};

int main()
{
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;
	cout << sizeof(D) << endl;
	system("pause");
	return 0;
}

 

我们知道D类对象为了解决菱形继承带来的数据冗余和二义性问题,采用虚继承,这时候对象中各部分的存储并不是我们预想的那样

但是B类对象和C类对象是不存在要解决这些问题的,预想大小应该为8(A中的成员,和自己的成员)

 

其实这里B也构造成虚表与虚指针这种形式,是因为要满足这种情况,

因为在公有继承中,子类对象是可以赋值给父类对象的

我们可以理解为,为了符合赋值时D的切片行为,B类对象可以找到A。

int main()int main()
{
	D d1;
	B b1;
	b1 = d1;
	
	system("pause");
	return 0;
}

 

下面留几个问题:

1.什么是菱形继承?

在多继承体系中,形成了一种B继承A,C也继承A,D继承了B和C,的继承关系,这样的话D类对象中就会有两个A类对象,(分别在B对象和C对象中)。

2.菱形继承存在什么问题?

数据冗余和二义性

如上面问题一,D类对象中有两个对象A,这样就会造成数据冗余,而且当你想访问A类对象中的成员时,不知道是想访问B中A类成员还是C中的A类成员,造成二义性。   

3.如何解决?     

虚拟继承

让B类虚拟继承A类,C类虚拟继承A类,D类再继承B类和C类时,就会解决数据冗余和二义性。

4.虚继承是如何实现的?

当B类虚拟继承A类时,B类对象中就会有一个虚基表指针,指向一张虚基表,这张续集表中存放B中A类对象相较于该位置的偏移量,C类对象也是,这样的话,当出现菱形继承时,D中就只存一份A类对象,D类对象的B和C类对象中分别有一张虚基表指针(指向一张虚基表,存放A类对象相较于该位置的偏移量),这样就避免了数据冗余,而且只有一份A类对象,也不存在二义性了。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值