《C++编程风格(修订版)》——3.3 继承关系

3.3 继承关系

C++编程风格(修订版)
现在让我们来看看程序中的继承关系。Stack通过一个保护数据成员vec来为每个派生类提供索引服务,其中vec指向的是一个void类型指针数组。当在派生类中执行入栈和出栈的操作时,Stack将调节私有数据成员top以在数组中移动。在派生类中又分配了一个数组来存储堆栈中的值,并将数组的地址保存在数据成员data中。此外,在派生类中还对vec进行了初始化,这样对于每一个i(只要在数组边界内),vec[i]将指向data[i]。从Stack::push()和Stack::pop()返回void类型指针将分别告诉派生类将对data中的哪一个元素进行入栈和出栈的操作。

图3.2 从vec指向data数组的指针

图3.2中给出了相关的数据结构,注意其中指针的形式是一致的。在这个数据结构中几乎没有包含任何信息。在派生类的构造函数中将执行下面的语句:

对于在data所指向数组中的每一个元素,在vec中都有相应的指针。在构造函数执行完之后,这些指针的值将不再改变。基类将通过这些指针来告诉派生类到什么地方去访问数据。在这种处理方中式存在着一些问题:在类中存在着过多的指针,但只是包含了少量的信息。

如果我们将注意力转移到函数IntStack::pop()中的类型转换上,那么就可以从另一个角度来看这个问题。

在上面的类型转换中,我们将Stack::pop()返回的void类型指针转换为int类型指针,以访问由IntStack::data指向的数组中的元素。类型转换是不安全的,为了避免这种潜在的危险,在编写程序时最好不要使用类型转换。我们可以很容易地去掉上面的类型转换,但这样作将会暴露出vec中的问题。通常的情况是,Stack::pop()返回vec[i],其中vec[i]指向的是data[i],然后再由IntStack::pop()来返回data[i]。如果Stack::pop()直接返回的是i而不是vec[i],那么就可以不需要进行类型转换。在获得了i值之后,IntStack::pop()就可以立刻得到data[i],而无需进行类型转换。因此,在Stack类的成员函数push()和pop()中,应该返回的是派生类真正需要的整数下标值,而不是在使用前需要进行转换的void类型指针。在程序清单3.2中给出了一个更简单的Stack,我们注意到,在IntStack和CharStack的成员函数push()和pop()中将不再进行类型转换。同时还注意到,Stack的名字被改为StackIndex,因为这个名字可以更好地描述这个类的抽象。

程序清单3.2 简化后的堆栈抽象

StackIndex类的新接口能够更好地反映出它所提供的抽象。这个抽象就是移动索引,从而告诉派生类到什么地方去访问数据。堆栈的索引行为信息被限制在StackIndex类中,而堆栈中具体的元素信息则被限制在派生类中。派生类与基类唯一的交流是通过整数类型的索引来进行的。在不损失功能的情况下,堆栈的抽象得到了进一步的简化。它的功能就只是维护一个索引,而由其他的派生类来提供具体的堆栈存储。

找出简单的抽象。

图3.3 简化之后的数据结构

最初的Stack::vec在StackIndex中是多余的,因此我们在类的定义中去掉了它。每个派生类都能够从StackIndex::push()和StackIndex::pop()返回的索引中得到需要的信息,因此也就不再需要在基类中保存指向在派生类中声明的数据的指针。图3.3中给出了现在的数据结构。简化后的程序可以进一步提高内存使用效率。在去掉了Stack::vec之后,程序就大大减少了在实现堆栈时需要的内存空间。在一个典型的32位系统结构中,整数的大小是4个字节,因此指针也是4个字节,与最初的版本相比,现在一个IntStack对象只使用了一半的内存空间:每个元素需要的是4个字节,而并非最初的8个字节。CharStack对象使用的内存空间则减少到了1/5,从每个元素由需要5个字节减少为只需要1个字节。

时间: 2024-09-15 21:27:53

《C++编程风格(修订版)》——3.3 继承关系的相关文章

《C++编程风格(修订版)》——3 不必要的继承

3 不必要的继承 C++编程风格(修订版)虽然我们在第2章中仔细地将类的接口与实现区分开来,但在编写继承的代码时却没有这样做.如果我们想理解派生类与基类之间的继承关系,那么很重要的一点就是对继承关系中的接口部分和实现部分进行独立的分析.在本章中,我们将讨论一个从表面上看来非常适合使用继承的示例.不过,在对基类和派生类的接口及实现进行详细研究之后,我们将对这个类的代码进行修改.

《C++编程风格(修订版)》——3.2 继承作用域准则

3.2 继承作用域准则 C++编程风格(修订版)基类Stack的公有接口如下所示: 在派生类中,我们可以看到和基类成员函数有着同样名字的成员函数.注意,其中的Stack::pop()和Stack::push()都不是虚函数.同时,我们还注意到,派生类成员函数中的参数类型与相应的基类成员函数中的参数类型并不匹配.例如,Stack::push()不带参数,而IntStack::push()的参数是一个整数.对于这样的函数,根据C++作用域准则,这就意味着派生类的成员函数将隐藏基类的成员函数,因为在派

《C++编程风格(修订版)》——导读

前言 C++编程风格(修订版) 本书采用一种统一的方法来给出所要学习的内容.通过研究示例程序--"编程风格示例"--来引入每个学习主题,这些示例程序通常在某些重要的方面存在着缺陷.在分析程序时,我们采取了与做代码交叉审查时一样的思路:在审查同事的代码时,我们要找出哪些问题是最需要改正的,以及对程序的哪些部分进行修改才能最大程度提升程序的整体性能.在本书中,我们将对每个示例程序做详尽的阅读和分析.读者在阅读书中对示例程序的分析之前,可以首先从自己的角度去分析程序中的问题,然后试着给出自己

《C++编程风格(修订版)》——3.4 封装

3.4 封装 C++编程风格(修订版) 新的堆栈抽象更简单,程序的代码量也更小,并且也使用了更少的内存.然而,在StackIndex及其派生类的关系中还存在着尚未暴露出来的问题.在本章的前面部分,我们已经注意到,派生类的成员函数push()和pop()隐藏了它们从基类继承而来的同名成员函数.不过,我们也可以在IntStack和CharStack对象上来调用这些基类函数,这就需要使用函数的完全解析名字:StackIndex::push()和StackIndex::pop().事实上,在它们各自的派

《C++编程风格(修订版)》——3.5 接口与实现

3.5 接口与实现 C++编程风格(修订版)为什么要使用继承?如果对继承关系作进一步的分析,我们会发现程序中的继承其实可以完全去掉.因此,第二种解决方案就是使用成员对象而不是继承. 在第2章中已经讨论过,一个C++类有着两个重要的方面:用于描述类行为的公有接口,以及行为的私有实现.大多数继承所采用的都是公有继承的形式:派生类同时继承了基类的接口和实现.不过,我们还可以有选择性地进行继承,即派生类可以只继承接口或者只继承实现.在私有基类中,派生类继承了所有的实现,但没有继承任何接口.而在继承公有的

《C++编程风格(修订版)》——3.1 编程风格示例:堆栈

3.1 编程风格示例:堆栈 C++编程风格(修订版)在程序清单3.1的程序中定义了一个用于处理字符堆栈的CharStack类,以及用于处理整数堆栈的IntStack类.我们将对这两个类进行分析和评价,并且判断这些类中的抽象是不是正确的.接口是否起到了相应的作用,以及类之间的继承关系是不是合适的. 有些读者在看到CharStack和IntStack时的第一反应可能是,这些类应该使用参数化类型来编写,这也是ANSI(参见Ellis和Stroustrup的著作,第341页)建议的方法.在这里我们暂时先

《C++编程风格(修订版)》——2.5 动态内存的一致性

2.5 动态内存的一致性 C++编程风格(修订版) 在程序清单 2.2 的 string 类中仍然存在着一些问题和不一致的地方.其中,在动态内存管理 上的不一致性与我们在前面所看到的不一致性是一样的,都是严重的问题.对于所有动态分配的 内存,我们都需要回答两个问题:首先,动态内存是不是足够大以容纳将要存储的信息?其次, 是不是所有的动态内存都是可回收的? 在默认构造函数中分配的字符数组肯定可以容纳空字符串: 这个构造函数所基于的假设是:在创建对象时将会为字符串分配内存,并且这个内存足以 容纳在对

《C++编程风格(修订版)》——2.2 明确定义的状态

2.2 明确定义的状态 C++编程风格(修订版) 在 string 类的前两个构造函数中存在着同样的问题. 如果在创建 string 对象时调用了上述两个构造函数之一,那么这个 string 对象的初始状态将 是未定义的.在下面的代码中将输出两个字符串,其中在创建 string 对象时分别调用了上面两个 构造函数. 对 x 和 y 来说,调用函数 print() 的结果是未定义的,因为由 x.s 和 y.s 指向的字符数组中的 内容是未定义的.在这两个构造函数中,都为字符数组分配了内存,但却没有

《C++编程风格(修订版)》——2.7 编程风格示例:第二种方法

2.7 编程风格示例:第二种方法 C++编程风格(修订版) 我们暂时先不去考虑去解决 string 类中的其他问题,而是将注意力转移到另一个不同的字符 串类.在这个类中,我们避免了大多数的上述问题.我们来分析程序清单 2.3 中的 SimpleString 类. 虽然 SimpleString 相对于 string 进行了改进,但仍然存在着一些缺陷. 程序清单 2.3 最初的 SimpleString 类 与 string 类一样,SimpleString 通过一个字符类型的指针 _string