一、类继承
面向对象编程的主要目的之一是提供可重用的代码,开发新项目,特别是十分庞大的项目时,重用经过测试的代码比重新编程要方便的多。传统的C函数库提供不是标准C库的其他库函数,一般不是开源的,对于用户来说不易根据自己的需求进行修改,即时给了源码也容易因修改而产生错误。C++类提供了更高级的重用性,很多厂商提供了类库,因为类由数据和方法组成,因此类库提供了比函数库更完整的程序包,通常类库都是提供源代码的,用户可以根据自己的需求进行修改。但是C++提供了一种比修改代码更好的一种方式。这种方法叫继承,它能够从已有的类派生出新的类,派生出的新类继承了原有类(基类)的特征,显然通过继承派生出新类要比白手起家新建一个新类要容易的多。
我们通过类的继承一般可以完成以下工作:
- 可以在已有的类的基础上添加功能。例如可以在数组类中添加数学运算;
- 可以给类添加数据;
- 可以修改类方法的行为。
对于上述工作我们可以通过复制类源代码进行修改,但继承机制只需提供新特性,甚至不需要访问源代码就可以派生出类。
继承是一种非常好的概念,其基本实现非常简单,但是要对其进行管理使之能够适应所有情况,我们需要作出一些调整。
二、一个简单的基类和继承
当我们定义了一个类TableTennisPlayer,要在此类继承派生出一个新类RatedPlayer,则此类称为基类,派生声明格式如下:
class RatedPlayer:public TableTennisPlayer
{
...;
}
冒号指出RatedPlayer类的基类是TableTennisPlayer类,public指出TableTennisPlayer是一个公有基类,这被称为公有派生。派生类对象包含基类对象,使用公有派生,基类的公有成员成为派生类的公有成员,基类的私有成员也将成为派生类的一部分,但只能通过基类的公有方法和保护方法访问。
上述代码让派生类对象存储了基类的数据成员并可以使用基类的方法。
派生类需要在继承特性中添加什么呢:
- 派生类需要自己的构造函数,构造函数必须给新成员和继承成员提供数据;
- 派生类可以根据自己的需要添加额外的数据成员和成员函数。
派生类构造函数的要点:
- 首先要创建基类对象;
- 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
- 派生类构造函数应初始化派生类新增的数据成员。
创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。派生类构造函数总是调用一个基类构造函数。可以使用初始化器列表语法指明要使用的基类构造函数,否则将调用默认的构造函数。
三、继承:is-a关系
C++有3种继承方式:公有继承、私有继承和保护继承。公有继承是最常用的方式,它建立了一种is-a关系,即派生对象也是一个基类对象,可以对基类对象执行的任何操作都可以在派生类对象执行。为阐明is-a关系,来看一些与该模型不符的例子,公有继承不建立has-a关系(一种包含关系);
公有继承不能建立is-like-a关系,它不能采用比喻,人们通常说律师像鲨鱼,但律师并不是鲨鱼,继承可以在基类上添加属性,但是不能删除属性;公有继承不建立is-imlemented-a关系;公有继承不建立uses-a关系。C++完全可以使用公有继承来建立has-a、ia-implemented-a或uses-a关系,但是这样做会导致编程方面的问题。
四、多态的公有继承
有时候我们会希望同一个方法在派生类和基类中的行为是不同的,这种复杂的行为称之为多态,即同一个方法的行为根据上下文不同,有两种重要的机制能完成多态的公有继承:
- 在派生类中重新定义基类的方法;
- 使用虚方法。
我们经常在基类中将派生类重新定义的方法声明为一个虚方法,如果没有使用虚方法,程序将根据引用类型或指针类型选择方法,如果使用虚方法,程序将根据引用或指针所指向的对象类型选择方法。为基类声明一个虚析构函数也是一个惯例。
五、静态联编和动态联编
程序调用函数时,在编译过程中进行联编称为静态联编,在程序执行过程中进行联编称为动态联编。
静态联编工作效率高,占用资源少,但是在我们在设计类时有时会需要在派生类中重新定义基类的一些方法,则将它设置为虚方法,否则设置为非虚方法。
调用虚函数时,程序会在内存和执行速度方面有一定的成本:
- 每个对象都会增大,增大量为存储地址的空间;
- 对于每个类,编译器都创建一个虚函数地址表;
- 对于每个函数调用,都需要执行一个额外操作,即在表中查找地址。
派生类不继承基类的构造函数,因此将类构造函数声明为虚函数没有意义;
通常应给基类提供一个虚析构函数,即时它并不需要析构函数。
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数,可以通过让友元使用虚成员函数来实现。
给基类方法进行重新定义时会隐藏方法,因此我们在重新定义继承方法时,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这种特性被称为返回类型协变,因为返回类型随对象类型变化而变化。
六、访问控制:protected
关键字protected与private相似,在类外只能用公有类方来访问protected中的成员。protected和private之间的区别只有在派生类中才会表现出来。派生类成员可以直接访问protected中的成员,但不能直接访问private中的成员。
最好对类数据成员采用私有访问,不要使用保护访问,同时通过基类方法使派生类能使用基类数据。对于函数成员,保护访问控制很好用,它让派生类能访问公众不能访问的私有函数。
七、抽象基类
抽象基类(ABC)描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征使用常规的虚函数来实现这种接口。纯虚函数是在常规虚函数原型后面添加=0。抽象基类一般包含派生类的共性特征。