一、友元函数的继承
友元函数不能被继承,就像爸爸的朋友不是你的朋友,如果要有友元函数,在子类重新定义一个。
二、静态成员的继承
静态成员的继承仍然是那个成员,普通成员的继承是不同的。
父类的静态成员属于当前类,也属于当前类的派生类。
三、单继承与多继承
1、单继承
一个子类只有一个直接父类
2、多继承
一个子类有两个或以上的直接父类
四、菱形继承
1、理解
如图 Assistant 就是一个菱形继承,显然如果不做特殊处理 Assistant 里面会有两份 Person 类中的成员,这就导致了数据冗余和二义性。
解决办法就是在菱形继承中父类被继承两次的地方进行虚继承virtual
被继承的 Person 类是虚基类。
2、剖析
(1)未用 virtual
cpp
class A
{
public:
int _a;
};
class B : public A
//class B : virtual public A
{
public:
int _b;
int _b1;
int _b2;
};
class C : public A
//class C : virtual public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
//d._a = 0;
d._b = 3;
d._c = 4;
d._d = 5;
}
上图就是典型的菱形继承且没有解决,这样在D类中我们发现两个_a是不一样的,要想使用必须指定类域,这就是数据冗余和二义性。
内存级理解:
明显的看到内存中D对象中的B,C存有不同的_a,这是不被允许的。
(2)用 virtual
cpp
class A
{
public:
int _a;
};
//class B : public A
class B : virtual public A
{
public:
int _b;
int _b1;
int _b2;
};
//class C : public A
class C : virtual public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._a = 0;
d._b = 3;
d._c = 4;
d._d = 5;
}
上图我们用虚继承解决了问题,此时类D中的_a是同一个_a,所以最后_a值应该是0
内存级理解:
首先B,C类中不再存有_a的值,而是用一个地址代替了,_a的值被放到了D对象的内存最下面,地址所指向的内容存放的是_a的值0,还有十六进制的偏移量,用来找到原内存中_a的存储位置。
这样就解决了问题。
(3)总结
a、存地址的偏移量是为了方便找到数据,方便切片。
b、虚继承的解决原理:把原来存父类成员的地方存地址,通过地址和偏移量找到唯一的父类成员。实现原来有几个类继承就有几份父类成员的数据冗余变成现在存几个地址和偏移量来找到内存中唯一的一份父类成员,解决了数据冗余和二义性。
c、对象的存储按声明顺序存储。
五、继承和组合
我们发现继承和组合都能复用之前的类,两者是相似的,在实践中两者也会一起使用。
cpp
//父类
class A {};
//继承
class B : public A
{
//直接继承,直接使用
};
//组合
class C
{
private:
A _aa; //创建 A 对象,使用成员及方法
}
|----|-------------------------------------------------------------------------|
| 方式 | 备注 |
| 继承 | is_a关系,白箱复用(在继承方式中,基类的 内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响)耦合度高。 |
| 组合 | has_a关系,黑箱复用(为对象的内部细节是不可见的)耦合度低。 |
优先使用组合,组合的耦合度低,代码维护性好。