C++ 多继承

目录

1.多继承

(1)概念

(2)复杂的菱形继承

(3)虚继承解决菱形继承问题

(4)虚继承的原理

内存演示

虚基表

2.继承与组合

(1)两者区别

(2)继承与组合的区别

(3)使用情况

3.总结


1.多继承

(1)概念

一个类有两个及以上父类时称这个继承关系为多继承。

class Student
{
public:
protected:
	int _id;
};

class Teacher
{
public:
protected:
	int _cource;
};

class Assistant :public Student, public Teacher
{
public:
protected:
protected:
};

我们使用逗号表示分隔,即继承多个父类。可以通过调试来观察子类Assistant的内容:

(2)复杂的菱形继承

菱形继承是多继承的一种情况:

菱形继承出现的问题:从对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

数据冗余指的是类Assistant中会有两份Person的成员,二义性指的是这两份成员每一次调用不知道调用的是哪一个,需要指定类域。

这段代码表示的就是菱形继承的关系:

class Person
{
public:
	string _name;
};
class Student :public Person
{
public:
protected:
	int _num;
};
class Teacher :public Person
{
public:
protected:
	int _id;
};
class Assistent :public Student, public Teacher
{
public:
protected:
	int _cource;
};
int main()
{
	Assistent a;
	return 0;
}

我们通过调试可以观测 a中的内容,发现会存在两份Person中的成员:

如果要对这两个Person成员赋值时,需要指定类域。

这就是所谓的二义性,在实际中一个人不能有两个名字,对于冗余性来说,如果Person中有一个很大的数组浪费的空间会很多。

(3)虚继承解决菱形继承问题

虚继承可以解决菱形继承的二义性和数据冗余问题。如上面的继承关系,在student和Teacher的继承Person时使用的虚拟继承,即可解决问题。需要注意的时,虚拟继承不要在其他地方去使用。

class Student :virtual public Person
{
public:
protected:
	int _num;
};
class Teacher :virtual public Person
{
public:
protected:
	int _id;
};

只需要在菱形的腰部两个父类假如virtual关键词即可

注意要在菱形的腰部。

当加完之后,在Assistant的对象中,Person类的_name成员就只有一个了。无论是否指定区域,更改的变量都只有一个:

(4)虚继承的原理

内存演示

要研究虚继承的原理,我们给出一个简化的菱形继承结构,再借助内存窗口观察对象成员的模型。

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 d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}
当没有使用虚继承(即没有使用virtual时)

我们使用内存窗口来观察:

通过观察内存中的布局,我们发现d中的B父类对象和C父类中的内容分别是连续存放的,B中有父类A中成员_a的值是1,其自己成员_b的值是3,两者的内存是挨着的,C同理,对于D类中自己的成员_d,放在了内存的最后。

确定d中B类对象和C类对象的存储顺序是根据继承顺序决定的。由于上述代码是class D:public B,public C,因此B类的对象会存在C类的前面。

而当我们给腰部加上virtual构成虚继承之后:

class B :virtual public A
{
public: 
	int _b;
};
class C :virtual public A
{
public:
	int _c;
};

使用virtual之后,我们发现已经将A中对象_a放在了最后,因此无论指定不指定类域,改变的都是同一个_a的值。

但同时我们发现了内存中多了两行,那么这两行是干什么的呢?

虚基表

从格式来看,这两行显然是都是地址。

我们在开辟一个内存2,向其中输入上面地址,我们发现地址中存储的内容是00 00 00 00,C类对象中同理,这里就不演示了。

这里00 00 00 00的意义在多态中会学到,注意看他的下一个位置存放的是00 00 00 14,这里是十六进制,因此表示的是20这个数字。

再来看内存1:

我们可以发现004FF96C和004FF980这两个地址之间刚好相差20个字节。

因此我们可以知道:在虚继承中,B类对象和C类对象的内存中新加入的是一个地址,分别用于寻找两者与A类型变量的偏移量。B类对象与A类对象的偏移量是20,同理可验证C类对象的偏移量是12。而内存2也有一个专有名词:虚基表、

总结:A一般叫做虚基类,在D里面,A类成员放在放在一个公共的位置,有时B要找A,C要找A,就要通过虚基表中的偏移量进行计算。

比如,当我们再用B类和C类建立两个变量:

	B b = d;
	C c = d;

此时会发生切片处理,需要将d中的A类对象赋值到b和c中,此时就需要使用到虚基表来寻找。

再比如:

	B* pb = &d;
	pb->_a = 10;

pb指向了d的首地址,要更改d中的_a的值,指针pb也需要使用虚基表来进行寻找。

2.继承与组合

(1)两者区别

首先我们要对继承和组合进行区分:

继承表示的是子类继承父类,组合表示的是一个类中定义另一个类的成员变量。

//继承
class A
{
public:
	int _a;
};
class B:public A
{
public:
	int _b;
};
//组合
class C
{
public:
	int _c;
};
class D 
{
public:
	int _d;
	C _obj;
};

(2)继承与组合的区别

我们需要明确一点:类之间,模块之间最好是低耦合,高内聚,因为方便维护。

低耦合:类之间依赖关系越弱越好。

高内聚:内部成员关机紧密。

1.继承对应于白盒:B可以直接使用A中的公有保护成员,破坏了封装性。

2.组合对应于黑盒:D只能使用C的公有,不能直接使用保护成员。

举一个例子:
如果A中有5个public,5个protected
对于组合来说,非基类只能使用这5个public,基类中的其他成员随便修改都不会影响该非基类。
对于继承来说,基类中一切的改变都会影响子类。
那可以抛弃继承的语法吗?当然是不行的。

多态是建立在继承的基础上的。

(3)使用情况

1.如果B就是一个A,比如Student是一个Person,我们称这种关系为is-a关系,此时适合使用继承。
2.如果D被包含于C,比如head包含eyes,我们称这种关系为has-a关系,此时适合使用组合。
3.当遇到特殊情况,is-a和has-a都可以讲通时,优先使用组合

3.友元与静态成员

这里只需要记住两点:

1.友元关系不能继承。

2.静态成员会被继承下来,无论继承多少,静态成员只有一个。

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值