2.继承结构

继承是面向对象编程语言当中,最重要的部分,也是代码重用的一种重要形式 。
1.基本形式
首先继承的有三种基本形式,分别是、、,代表公有继承、私有继承和保护继承,之前在介绍作用范围的时候提过这三者的区别 , 但这并不重要 。就以目前我个人遇到过的代码来说,99%的情况下,全部使用的都是公有继承(),另外两种极少用到,建议大家不用关注,遇到了再查找资料 。
继承的基本写法如下 , 在子类/派生类后面跟上":",再加上继承形式,最后是被继承的父类/基类 。
class Derivation : public Baseclass Child : public Father
父类、子类、基类、派生类,这几个概念看起来比较复杂,其实关系就像上面的代码所显示那样,已经存在的类叫做基类/父类,继承后的类叫子类/派生类 。通常我们画类图的时候 , 都是从子类/派生类指向基类/父类 。
那么继承意味着什么?
以最最常规的情况来说,继承实际就相当于原封不动的复制了一份父类/基类的信息(包括函数和数据)到子类/派生类当中 , 就像孩子继承父亲的财产 。例如上面的情况,父类/基类当中存在和dara1,如果使用了继承,那么子类/派生类就相当于默认在当中添加了和data1,且它们的权限和父类/基类一模一样(对于继承来说) 。
在代码当中,对于A当中的元素,用同样的方法在B当中也可以使用 。
class A {public:int m_a;};class B :public A {//public://int m_a;//由于继承 , 这部分内容是隐含的};int main() {B b;b.m_a = 1;cout << "b.m_a = " << b.m_a << endl;return 0;}
显然,通过这样一种方式来处理那些重复性的函数和数据,可以极大的减少我们的工作量 , 因为类B当中可以包含A的全部信息 。否则对于B类,我们需要在它当中手动添加这些重复性的函数和数据,这就是继承对于代码重用性的体现 。
不过继承也不能滥用,当出现B is A即B是A的时候 , 一般可以使用继承 。
需要注意的是,继承所带来的函数和数据,对于代码编辑器来说,不是直接写在当前类里面的 。也就意味着,查看代码时,如果出现了继承,我们就需要去到基类/父类当中查看当前类所含有的额外信息 。
2.继承结构
上面只是继承的基本结构,它只代表了类A和B之间的关系,实际使用时要复杂得多 。将多个类继承进行嵌套和组合使用,这样就有了多级继承和多重继承 。
2.1.多级继承
像愚公移山那样,子又生孙,孙又生子,子又有子 , 子又有孙,子子孙孙无穷匮也 。继承也可以形成一条链路,B继承自A , C继承自B,D继承自C 。。。
class A {};class B :public A {};class C :public B{};
其中B包含A的全部信息,C又包含B的全部信息,因此C其实包含了A和B的全部信息 , 以此类推,每一次继承,不管这条链路有多长,子类/派生类都可以获得链路上面所有父类/基类的信息,这就是多级继承 。
2.2.多重继承和单一继承
虽然在生物学上只能有一个父亲 , 但在“财产继承”上却不是 , 没人规定我们只能继承一个先祖的财产 。所以就会出现,单个类继承多个父类./基类的情况 , 这就被称为多重继承 。在程序当中使用逗号","隔开这些父类/派生类 。
class A {};class B {};class C :public B,public A{};
一个类继承自多个父类/派生类,那么自然有另外一种情况,一个类继承给多个子类/基类 , 就像是一个父亲有多个孩子那样 。
这没有特殊的名称,是单一继承 。所谓的单一和多重都是基于子类/派生类来说的 。
class A {};class B : public A{};class C : public A{};
2.3.继承链路
基于上述的不同继承结构,在实际使用当中,都是组合使用,以此构建出复杂且庞大的继承链路结构 。
3.元素重名 3.1.单一类被多次继承
假如我在设计时不小心故意地造成了以下情况:
B继承了A,且C又继承了A和B,那对于C来说,A就被继承了两次 。
那么问题来了,如果还是按照基本形式来理解 , 即将A的内容在B当中重新写一遍,那么并不合理,因为这样会出现一模一样的元素 , 相当于C当中有两份一模一样的内容来自A 。
这种情况的确是被允许的,A的内容会因此被拷贝为两份,对于C来说就相当于无中生有,凭空多出来了一份内容 。但是情况就没有那么简单了 , 在实际访问时没有办法像常规的继承那样直接访问,因为A无法确定继承的内容是继承自A还是来通过B继承自A的 。
需要在访问时使用","指定继承的来源,再使用"::"访问对应成员 。这一用法对于一般情况也适用 , 但是并没有太大意义 。
class A {public:int m_a;};class B :public A {};class C : public B, public A {public:C() {} ;};int main() {C c;//c.m_a; //错误,无法确定元素来源c.A::m_a = 1;c.B::m_a = 2;c.B::A::m_a = 3;cout << "c.A::m_a = " << c.A::m_a << endl;// 输出1cout << "c.B::m_a = " << c.B::m_a << endl;// 输出2cout << "c.B::A::m_a = " << c.B::A::m_a << endl;// 输出3return 0;}
在上面的例子当中,如果定位到A的内容是来自B时 , 就已经确认了是来自B继承的A 。因此c.B::m_a和c.B::A::m_a所表示的内容是一样的,
3.2.重名
假如我在设计时故意不小心让子类/派生类和父类/基类当中的元素名字一样,那又会怎么样?
class A {public:int m_a;};class B :public A {public:int m_a;};int main() {B b;b.m_a = 1;b.A::m_a = 2;cout << "b.m_a = " << b.m_a << endl;// 输出1cout << "b.A::m_a = " << b.A::m_a << endl;// 输出2return 0;}
这个时候 , 同样可以保存两份内容,直接进行访问就是自己的元素,指名的话就是对应的父类/基类的元素 。
但是,必须要强调 。原则上,我们不应当这种出现元素重名的情况,这是写代码当中应当避免的,这对于代码的编写和维护都是灾难,如果出现这种情况,最好想办法重新调整下名字或者架构 。
4.补充
其它一些性质作为补充:
【2.继承结构】4.1.友元关系无法继承 4.2.对于静态成员来说 , 无论如何继承,整条链路上始终只能存在一个 4.3.子类/派生类的前向声明不需要加上继承的元素