如果要理解派生类和基类之间的继承关系,那么很重要的一点就是分别对继承关系中的接口部分和实现部分进行分析。下面是一个表面上非常适合使用继承的示例,不过,在对基类和派生类的接口和实现进行了详细的研究后,我们会对这个类的代码进行大量改进。闲话少说,上代码!
上述代码的缺点:
1. 派生类中的同名函数覆盖了基类的函数。也就是说:IntStack和CharStack都继承了基类Stack的公有接口,但却用它们自身的函数把接口都隐藏起来了。
2. 在类中存在过多的指针,而只包含了少量的信息。
3. 程序在返回值的时候过多的依赖了类型转换,类型转换是不安全的,应该避免使用。
改正上述缺点后的程序为:
上述代码虽然更为简单,程序的代码量也更少,并且也使用了更少的内存,然而依然存在这不少缺陷,例如:
1. 当我们通过基类的指针或引用来使用派生类IntStack或CharStack的对象时,我们调用的将是基类的push或pop函数。
2. 与继承相关的基类的析构函数并没有声明为虚函数。如果我们动态的创建了一个IntStack对象,并通过基类型的指针来删除这个对象时,将只会调用基类的析构函数,从而导致内存泄露。
改进的方法有两个:
1. 将StackIndex作为一个私有继承的基类。私有继承不但能够防止基类的公有接口成为派生类公有接口的一部分,还能够防止将基类型的引用或指针指向派生类对象。
2. 使用成员对象而不是继承。关于继承:在私有基类中,派生类继承了所有的实现,但没有继承任何的接口;在公有继承中,派生类同时继承了基类的接口和实现;在继承公有的抽象基类时,派生类继承了所有的接口,但所继承的实现可能是不完整的或者是不存在的。
在上面的例子中,派生类与其私有基类之间的关系其实类似于客户类与服务器类的关系。在本例中,选择私有基类的形式与选择成员对象的形式相比,这两种方法功能上完全等价。但成员对象与继承相比,语义要更加清晰,成员对象是一种更好的选择。
注意到CharStack和IntStack中重载构造函数的相似性,我们应该考虑使用默认参数的形式来代替函数重载,下面是使用成员对象的完整代码: