C++的一个主要目标价就是促进代码重用。公有继承是实现这种目标的机制之一,但这并不是唯一的机制。当然还有其他的方法,其中之一是使用这样的类成员:本身是另一个类的对象。这种用方法被称为包含、组合、或层次化。另一种方法是使用私有或保护继承。
多重继承使得能够使用连个或更多的基类派生出新的类,将基类的功能组合在一起。
包含成员对象的类。
valarray类
这个类的一些方法:
operator[]():访问各个元素
size();返回包含的元素数。
sum();返回所有元素的总和
max();返回最大的元素。
min():返回最小的元素。
接口和实现
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数铜接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
C++和约束
C++包含让程序员能够限制程序结构的特性——使用explicit防止但参数构造函数的隐式转换,使用const限制方法修改数据,等待。这样做的根本原因是:在编译阶段出现错误优于在运行阶段出现错误。
初始化顺序
当初始化列表包含多个项目时,这些项目被初始化的顺序为他们被声明的顺序,而不是他们在初始化列表中的顺序。
私有继承
C++还有另一种实现has-a关系的途径——私有继承。使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用包含还是私有继承
由于既可以使用包含也可以使用私有继承来建立has-a关系。那么应使用哪种方式呢?大多数C++程序员倾向使用包含。首先,它易于理解。类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象,而使用继承将是关系更抽象。其次,继承会引起很多问题,尤其从多个基类继承时,可能必须处理很多问题,如包含同名方法的独立的基类或共享祖先的独立基类。总之,使用包含不太可能遇到这样的麻烦。另外,包含能够包括多个同类的子对象。而继承只能使用一个这样的对象。(当对象都没有名称时,将难以区分)。
然而,私有继承所提供的特性确实比包含多。例如,假设类包含保护成员(可以是数据成员,也可以是成员函数),则这样的成员在派生类中是可用的,但在继承层次结构外是不可用的。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承得到的将是派生类,因此它能够访问保护成员。
另一种需要使用私有继承的情况是需要重新定义些虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将之内个在类中使用,而不是公有的。
提示:通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要从新定义虚函数,则应使用私有继承。
多重继承
如果类有见解虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式的调用该虚基类的某个构造函数。
警告:多重继承可能导致函数调用的二义性
非公有继承能够建立is-a的关系,这样派生类可以重用基类的代码。私有继承和保护继承也使得能够重用基类的代码,但建立的是has-a的关系。
时候用私有继承时,基类的公有成员和保护成员将成为派生类的 私有成员;使用保护继承时,基类的公有成员和保护成员将成为派生类的保护成员。
无论使用哪种继承,基类的公有接口都将成为派生类的内部接口。这有时候被称为继承实现,但并不继承接口,因为派生列对象不能显式的使用基类的接口。因此,不能将派生对象看作是一种基类对象。
由于这个原因,在不进行显式类型转换的情况下,基类指针或引用将不能指向派生类对象。
还可以通过开发包含对象成员的类来重用类挨骂。这种用方法被称为包含、层次化或组合,它建立的也是has-a的关系。
与私有继承和保护继承相比,包含更容易实现和使用,所以通常有限采用这种方式。然而,私有继承和保护继承比包含有一些不同的功能。例如,继承允许派生类访问基类的的保护成员;还允许派生类重新定义从基类那里继承的虚函数。因为包含不是继承,所以通过包含来重用类代码时,不能时候用这些功能。另一方面,如果需要使用某个类的几个对象,则用包含更合适。
多重继承使得能够在类设计中重用多个类的代码。私有MI或保护MI建立has-a关系,而公有MI建立is-a惯性系。
MI会带来一些问题,即多次定义同一个名称,继承多个基类对象。可以使用类限定符来解决名称二义性的问题,使用虚基类来避免继承多个基类对象的问题。但使用虚基类后,就需要为编写构造函数初始化列表以及解决二义性问题引入新的规则。
类模板使得能够常见通用的类设计,其中类型(通常是成员类型)有类型参数表示。典型的模板如下:
template <class T>
class Tc
{
T v;
...
public:
Tc(const T & val) : v(val) {}
...
};
其中T是类型参数,用做以后将指定的实际类型的占位符(这个参数可以是任意有效的C++名称,但通常使用T 和Tpye)。这种情况下,也可以使用typename代替class;
template <typename T>
class Rev{...};
类定义(实例化)在声明类对象并指定特定类型时生成。例如,下面的生命导致编译器生成类声明,用声明中的实际类型short替换模板中的所有类型参数T
class Ic<short> sic;
这里,类型为Ic<short>而不是Ic。Ic<short>被称为模板具体化。具体的说,这是一个隐式实例化。
类模板可以指定多个泛型,也可以有非类型参数:
template <class T, class TT, int n>
class Pals{...};
模板类可以用作其他类,结构和模板的成员。
所有这些机制的目的都是为了让程序员能够重用经过测试的代码,而不至于手工复制它们。