C++ 继承
面向对象编程的主要目的之一是提供可重用的代码。开发新项目时,尤其当项目特别庞大时,重用已经测试的代码比重新编写新的代码要好的多。C语言中,有传统的C函数库、预定义、预编译的函数、专用的C库。C++类提供了更高层次的重用性,提供了比修改代码更好的方法来扩展和修改类——类继承。它能够从已有的类派生成新的类,而派生类继承了原有类的特征,包括方法。
下面是可以通过继承完成的工作:
• 可以在已有类的基础上添加功能。例如对于数组类,添加数据数学运算。
• 可以给类添加数据。例如对字符串类,派生出指定字符串显示颜色的数据成员。
• 可以修改类方法的行为。例如对于飞机上的乘客passenger类,派生出更高级别服务的FirstClassPassenger类。
从一个类派生出另一个类时,原始类被称为基类,继承类被称为派生类。
继承的三种方式
派生类和基类的特殊关系是基于C++继承的底层模型的。C++有三种继承方式:公有继承、保护继承和私有继承
公有继承是最常用的方式,它建立的是一种is-a
关系,这种关系就是is a kind of(是一种)。所有使用公有继承派生的派生类需要符合is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的操作,也可以对派生类对象执行。例如有一个Fruit类,由于香蕉是一种水果,所以可以从Fruit类派生出Banana类。新的Banana类可以添加专门用于香蕉的成员。
下面是不符合is-a关系的例子:
公有继承不确定has-a
关系。例如午餐包括水果,但午餐不是一种水果,这里Lunch和Fruit不是is-a关系,所以不能从Fruit类使用公有继承派生出Lunch类。在午餐中添加水果的正确方法是has-a关系:午餐有水果。比较好的方法就是在Lunch类中包含一个Fruit类成员。
公有继承不确定is-like-a
关系,即:它不采用比喻。人们说,女人像老虎,但女人实际上不是老虎。所以不应该从Tiger类通过公有继承派生Women类。即:公有继承是在基类的基础上添加属性,但不能删除基类的属性。对于这种情况的解决方法是使用抽象基类,后面会介绍。
公有继承不建立is-implemented-as-a
(作为……来实现)的关系。例如可以用数组来实现栈,但从Array类派生出Stack类是不合适的。正确的方式是:让栈包含一个私有Array对象来隐藏数组实现。
公有继承不建立use-a
关系,例如计算机可以使用打印机,但无论是从Computer派生Printer还是反之都是没有意义的。正确的方式是:通过友元函数或类来处理Printer对象和Computer对象之间的通信。
如果用公有继承来实现上述4种例子通常会出现编程方面的问题,所以还是坚持公有继承只用于实现is-a
关系吧!
简单的基类和派生类
如果某健身俱乐部需要一个程序跟踪乒乓球会员,先需要设计基类TableTennisPlayer。
1 | // tabtenn0.h -- a tabletennis base class |
派生一个类。俱乐部里的一些成员参加过当地锦标赛,所有在比赛中有过比分。与其从零开始设计新的类,不如从TableTennisPlayer中派生一个类。
1 | class RatedPlayer : public TableTennisPlayer |
冒号指出RatedPlayer
的基类是TableTennisPlayer
。这种特殊的声明头表示TableTennisPlayer是一个公有基类,这被称为公有派生。
继承了什么?
使用公有派生,基类的公有成员将成为派生类的公有成员,基类的私有部分成为派生类的一部分,但只能通过基类的公有和保护方法访问。
派生类类声明代码做的工作:
• 派生类对象存储了基类的数据成员(派生类继承基类的实现)
• 派生类对象可以使用基类的方法(派生类继承基类的接口)
派生类需要在继承特性中添加:
• 派生类需要自己的构造函数。
• 派生类可以根据需要添加额外的数据成员和成员函数。
初始化列表——关于派生类构造函数的考量
派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。创建派生类对象时,程序首先创建基类对象。这意味着基类对象应当在程序进入派生类构造函数之前被创建,C++使用成员初始化列表语法完成这种工作。
如果不使用成员初始化列表,由于必须首先创建基类对象,程序就使用默认的基类构造函数。
第二个构造函数是用基类对象引用作为参数的派生类构造函数。tp类型是TableTennisPlayer & ,因此这里调用的是默认复制构造函数。
有关派生类构造函数的要点如下:
• 首先创建基类对象。
• 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数。
• 派生类构造函数还应初始化新增的数据成员。
派生类和基类的关系
- 派生类兑现可以使用基类的方法,条件是方法不是私有的。
- 基类指针可以在不进行显示转换的情况下指向派生类对象。
- 基类引用可以在不进行显示转换的情况下引用派生类对象。
- 不可以将基类的对象和地址赋给派生类引用和指针。
基类引用和指针指向/引用派生类对象。如果有方法的形参是基类引用,就可以指定基类对象或者派生类对象。如果形参是指向基类的指针的,就可以使用基类对象的地址或派生类对象的地址作为实参。
1 | // tabtenn1.h -- a tabletennis base class |