C++继承(下)
文章目录
- 1. 继承与友元
- 2. 继承与静态成员
- 3. 复杂的菱形继承及菱形虚拟继承
- 3.1 虚拟继承解决数据冗余和二义性的原理
- 4. 继承和组合
1. 继承与友元
举个例子:
从上面可以看出:
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
如果我们想让这个函数能调用子类的成员,我们也需要让这个函数也成为子类的友元:
2. 继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
举个例子:
这里我们让Graduate继承Student,让Student继承Person。
然后我们再用这些类去定义一些对象,这样每定义一个对象里面都有父类继承下来的count成员变量,也就会调用到Person类的构造函数。
从这里我们也可以看到结果证明:无论派生出多少个子类,都只有一个static成员实例。
3. 复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
这个多继承是什么意思呢?比如说:在大学中,你是一名研究生,你的身份既是一名学生,也是一名老师。这样就有了两种关系,也就继承了两个类。
菱形继承:菱形继承是多继承的一种特殊情况
而菱形继承的形成是因为学生身份和老师身份又去继承了人这个身份。但是这样会出现一些问题:
菱形继承的问题:从上面的代码中,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
这样,我们定义一个对象去调用_name,我们不知道调用的是那一个。该怎么解决呢?我们来看:
虽然解决了二义性的问题,但是没有解决数据冗余的问题。而数据冗余会给我们造成空间浪费。
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其它地方去使用。
3.1 虚拟继承解决数据冗余和二义性的原理
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型。
我们以这份代码为例。但是这里我们要注意一个问题:我们要看内存窗口,监视窗口被vs优化后的,看起来可能不是太准确。
可以看到里面有两个_a。然后我们看看虚拟继承的内存图:
我们看到此时_a放到最下面了。
B里面的_a放在了这里。
C里的_a也放在这里。
它的解决方法是把这个_a放到了一个公共区域。那么此时这里B,C里面的两个指针是干嘛的呢?我们来看一下:
我们看到这个指针指的是空,下面是一个十六进制的14,转换成10进制为20。
它指向的也是空,下面十六进制的0c,转换成十进制为12。
那么这两个数字是干什么的呢?答案是:它们是距离_a存储位置的偏移量。
可以看到B的指针(0x00F4F988)加上20,就会指向_a(0x00F4F99C)。
可以看到C的指针(0x00F4F990)加上12,就会指向_a(0x00F4F99C)。
这里的作用就是找到菱形虚拟继承的成员变量的位置。这两个指针也被叫做虚基表指针,指针指向的表叫做虚基表。
可能有的同学会问:为什么D中B和C部分要去找属于自己的_a?
那么大家看看当下面的赋值:
D d;
B b = d;
C c = d;
此处的赋值,B和C都需要找到d里的_a才能赋值过去。
那么用static来修饰成员变量能不能解决数据冗余的问题呢?
答案是:不可以。
两个D对象,它们的_a是不一样的。
但是static定义的_a两个是一样的。所以这里不能用static解决问题。
4. 继承和组合
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
举个例子:
那么什么时候使用继承,什么时候使用组合呢?
像人<-学生,动物<-狗这些我们比较适合使用继承。车->轮胎,电脑->键盘我们这些可以使用组合。而两者都可以使用的时候,优先使用对象组合。
为什么优先使用对象组合呢?
继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。
术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
像父类的公有成员子类可以直接用,父类的保护成员子类也可以直接用。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse)。
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。。
像C类的公有成员D可以直接用,C类的保护成员D不可以直接用。