面向对象编程的主要目的之一是:提供可重用的代码。尤其是项目很庞大的时候,重用测试过的代码比重新编码代码要好得多。
C++提供了更高层次的重用性。其中之一就是继承这个概念。
一些厂商提供了类库。类库由类声明和实现构成。类库通常是由源代码组成的,这意味着可以对其进行修改,以满足需求。但是也可以用类继承的方式,来扩展和修改类。这比修改代码的方法更好。正如继承一笔财产要比自己白手起家更容易。
继承的基本实现非常简单,但是要对继承进行管理,使之在所有情况下都能正常使用,则需要做一些调整。所以继承有简单的一面,也有复杂的一面。
一个简单的类开始
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。
基类的私有部分将称为派生类的一部分,但只能通过基类的公有和保护方法访问。
基类的公有成员将称为派生类的公有成员。
派生类对象存储了基类的数据成员(派生类继承了基类的实现);
派生类对象可以使用基类的方法(派生类继承了基类的接口);
派生类需要添加自己的构造函数;
派生类可以根据自身需要添加额外的数据成员和成员函数;
构造函数:访问权限的考虑
派生类不能访问基类的私有成员,而必须通过基类方法进行访问。
因此派生类构造函数必须使用基类构造函数;
创建派生类对象时,程序首先创建基类对象。从概念上讲,这意味着基类对象应当在程序进入派生类构造函数之前被创建。->C++使用成员初始化列表来完成这种工作。
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer(fn,ln,ht)
{
rating = r;
}
如果省略了基类构造函数的话:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht)
{
rating = r;
}
首先还是创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数。
因此上述代码与下面等效:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer()
{
rating = r;
}
除非要使用默认构造函数,否则应该显式调用正确的基类构造函数。
还有一种构造函数代码:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer(tp)
{
rating = r;
}
这里也是将TableTennisPlayer的信息传递给了TableTennisPlayer构造函数。
这种方式将调用基类的复制构造函数,如果基类没有定义复制构造函数,但又要使用它,则编译器将自动生成一个。
甚至还可以对派生类成员使用成员初始化列表语法:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer(tp),rating(r)
{
}
派生类构造函数的要点总结:
首先创建基类对象;
派生类构造函数应通过成员初始化列表(TableTennisPlayer(tp))将基类信息传递给基类构造函数;
派生类构造函数应初始化派生类新增的数据成员(rating = r)。
如果没有提供显示构造函数,将使用隐式构造函数。
理解派生类对象创建过程的对象创建顺序:先创建基类对象,在创建派生类对象;
释放对象的顺序与创建对象的顺序相反:首先执行派生类的析构函数,然后自动调用基类的析构函数。
成员初始化列表:(TableTennisPlayer(tp))
派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。
derived :: derived(type1 x, type2 y) : base(x,y)
{
...
}
Derived是派生类,base是基类。X和Y是基类构造函数使用的变量。
如果派生类构造函数接收到参数10和12,这种机制将把10和12传递给定义为接受这些类型的参数的基类构造函数。类只能将值传递回相邻的基类。虚基类除外,虚基类可以使用相同的机制将信息传递给相邻的基类,以此类推。如果没有在成员初始化列表中提供基类构造函数,程序将使用默认的积累构造函数。成员初始化列表只能用于构造函数。
使用派生类
使用派生类,程序必须要能够访问基类声明。可以把两个类的声明放在同一个头文件中,也可以放在不同的头文件中。
派生类和基类之间的特殊关系
派生类可以使用基类的方法,条件是方法不是私有的。
基类指针可以再不进行显式类型转换的情况下指向派生类对象。
基类引用可以再不进行显式类型转换的情况下引用派生类对象。
但是基类指针和引用只能调用基类的方法。
C++中要求引用和指针类型与赋予的类型匹配,但这一规则对继承来说是例外。这例外是单向的,也就是说不能将基类对象和地址赋给派生类引用和指针。
但是这种关系是单向的,不能将基类对象和地址赋给派生类引用和指针。
这样要求是有道理的:如果允许基类引用隐式地调用派生类方法,则可以使用基类引用为派生类对象调用基类的方法。因为派生类继承了基类的方法,所以这样做不会有问题。
如果可以将基类对象赋给派生类引用,将发生什么情况?派生类引用能够为基类对象调用派生类方法,这是没有意义的。例如TableTennisPlayer没有rating成员。
基类引用和指针可以指向派生类对象,将出现一些很有意思的现象: