组合与继承
c++最重要的特性之一就是对代码的重用,除了拷贝,c++应该具有更深入的功能
1、组合和继承
组合是简单地创建一个包含已存在的类对象的新类,新类是由已存在的类对象组合而成的;继承是以一个已存在类为基础创建一个新类,增加或重写成员函数,增加成员变量,但不缩减它的内容,称为继承;
组合与一般的类区别不大,只是成员变量有部分对象,而不是一味的内置类型。
2、简单继承
最简单的继承,无任何虚函数,基类与派生类无任何相同名字的成员变量和成员函数,public派生时,对函数的使用就按照名字来,但是当派生类重定义了与基类相同的成员变量或函数时,使用基类的函数要按照(基类名::函数或基类名::变量)的方式调用,对基类函数的重定义会把基类版本隐藏,类外不能使用基类版本。
成员重定义与名字隐藏:简单继承里,基类和派生类要看成两层,每一层内,函数可以重载,但是一旦派生类的函数重定义了基类函数(可以认为是只要用了函数名),那么基类版本的此名字的函数都被隐藏,只能通过域操作符并在派生类内进行访问;
不能继承的函数:构造函数和析构函数都是用来处理当前类的对象的,它们只对当然类有意义,所以继承时,构造函数和析构函数是不会被继承的;operator=也不能被继承;
3、构造函数初始化列表
正常情况下,编译器可以调用组合中子对象的默认构造函数来构造子对象,但如果没有默认构造函数或者需要用另一个版本的构造函数时就需要在初始化列表中完成;组合和继承的构造函数初始化列表一样,组合使用子对象名,继承使用基类名;内置类型的对象也可以放到这个初始化列表中,使用方式一样,当然它并不是调用构造函数,而是简单的赋值初始化,如果不显性初始化,这些变量不会自动初始化;
4、继承和组合的联合
组合与继承同时使用时,可以简单认为组合中的子对象是内置类型的变量,这样就可以简化看做是非联合的普通继承行为;
构造与析构:构造从类层次最根处开始,而每一层中,先构造基类,再构造子对象,其中子对象构造顺序按照类内声明的顺序而非初始化列表顺序;调用析构时,严格反向调用;
5、多重继承
上面介绍我们可以从一个已有类继承,那么我们也就应该能同时从多个类继承,实际上这是可以做到的,但是否它象设计部分一样有意义仍是一个有争议的话题。不过有一点是可以肯定的:直到我们已经很好地学会程序设计并完全理解这门语言时,我们才能试着用它。这时,我们大概会认识到,不管我们如何认为我们必须用多重继承,我们总是能通过单重继承来完成实现这样的需求。起初,多重继承似乎很简单,在继承期间,只需在基类表中增加多个类,用逗号隔开。然而,多重继承是有很多含糊的可能性;
6、继承的优点
继承是以那些已经存在的能很好运行的类为基础,丰富或者重新实现它们,这就意味着,我们可以把基类作为地基,然后在其上实现我们的建筑,如果出了问题,也可以肯定是我们本层的实现引发的错误,更全面地看,是一种层次化的递进,地基很抽象,建筑很具体;
认识到程序开发是一个渐增过程,就象人的学习过程一样,这是很重要的。我们能做尽可能多的分析,但当开始一个项目时,我们仍不可能知道所有的答案。如果开始把项目作为一个有机的、可进化的生物来“培养”,而不是完全一次性的构造它,使之像一个玻璃盒子式的摩天大楼,那么我们就会获得更大的成功和更直接的反馈。虽然继承是有用的技术,但在事情稳定之后,我们需要用新眼光重新审视一下我们的类层次,把它看成可感知的结构。记住,继承首先表示一种关系,其意为:“新类是老类的一个类型。”我们的程序不应当关心怎样摆布比特位,而应当关心如何创建和处理各类型的对象,以便用问题的术语表示模型。(没有更好的描述来总结这段话了)
7、向上映射
更深入地思考,继承不仅仅是让派生类能访问基类的成员,而是意味着派生类是一个特殊的基类,是一个特殊实例化的基类,这点很重要,这个特殊实例并不一定是基类子集,而是更具体的扩展集
7.1 向上映射:也就是一个接收基类对象的地方,可以将一个继承类的对象传递给它,向上映射是安全的,因为基类总不会比派生类缺少内容;指针和引用也支持向上映射,即基类指针或引用支持接收派生类对象的地址或引用;同样的,向下映射也是可行的,但是涉及一些问题的讨论,我们在17章说明这个问题;
引发的危机:对于隐含的类型转换,编译器只能认可形参的类型,所以函数内部对基类对象的成员函数的调用就是基类实现的版本,而无论传递的实参是什么类型对象,这显然并不是我们想要的结果。
选择组合和继承时,是否需要向上映射也是抉择哪一个的依据;
注意:简单继承中,基类函数与派生类函数都是实实在在的函数(即使派生类重定义了部分函数),所以编译器在编译时就确定了该调用哪个函数,传入的对象实参实际上是某类的数据成员部分,也就是函数最终使用的是对象的数据部分;(可以用没有虚函数表这个东西来解释简单继承所引发的向上映射的实际效果)