1.2.3 抽象数据类型
信息隐藏与封装这两个概念是与抽象数据类型(Abstract Data Type, ADT)相关的。假设我们要设计一个WordProcessor类,为使该类便于使用,我们将类的公有接口与私有实现区分开,在公有接口中提供诸如Save、Print等高层功能,在私有实现中设计一些低层支持函数,为公有接口中的函数提供服务。从这种意义上来说,我们设计的这个类就是一个抽象数据类型。当一个数据类型仅暴露其公有接口,而将其私有实现隐藏起来,我们就称这个数据类型是抽象的。C++(www.cppentry.com)通过对类的支持提供了信息隐藏,而信息隐藏,特别是对低层实现细节的隐藏,是构造抽象数据类型的关键。而且,在抽象数据类型中提供某个功能的办法就是将该功能以类的成员函数的形式封装起来。
例1-1
假设Stack类的公有接口包含如下成员函数:
若堆栈未满,在堆栈中插入一个元素。这种操作通常被称为压入。
若堆栈非空,从堆栈中移出最后被插入的元素。这种操作通常被称为弹出。
若堆栈非空,访问但不删除堆栈中最后被插入的元素。该元素在堆栈的位置称为栈顶。
Stack是一种后进先出(Last In First Out,LIFO)表,其插入和删除操作都在同一端(即top)进行。图1-2展示了一个字母在Stack的三个状态:开始,Stack是空的;然后,A和B通过push(A)和push(B)操作插入到Stack中;最后,在这个Stack执行一次pop操作,由于B最后插入,该pop操作将B删除,结果是A位于栈顶。
Stack类的上述这些操作是高层的,因为他们不需了解堆栈的底层实现,因此Stack类是一种抽象数据类型。
抽象数据类型使程序设计人员不需了解具体实现细节。抽象强调的是:一个数据类型如果是抽象的,那么使用者可以从其底层实现细节中抽象出来,或者说使用者无须了解其底层实现细节。抽象数据类型提供了一些规则,通过这些规则来展示该数据类型的一些性质。例如,上述的Stack类有如下一些规则:当堆栈已满而试图进行压入操作则发生上溢,当堆栈已空而试图进行弹出操作则发生下溢,这两种情况都是不允许的。只有进行了至少一次成功的压入操作后才可能产生成功的弹出操作。堆栈中的对象个数等于有效压入操作次数减去有效弹出操作次数。如果在第一次压入操作之后,压入操作次数总是大于弹出操作次数,则堆栈只有在第一次压入操作之前才是空的。
|
| 图1-2 堆栈的三个状态 |
使用抽象数据类型将提高程序的可靠性和健壮性。例如,假设我们要编写一个需要进行精确的整型算术运算的程序,如果我们直接使用底层的内建数据类型如int或long,那么有关数值范围的处理细节将由我们自己来完成。和C语言一样,C++(www.cppentry.com)中数据类型的取值范围与特定的系统相关,比如在某个系统中int型数据的最大可取值是2 147 483 647,我们必须进行一些编码,以保证计算结果不会超过这个值(整数溢出)。如果使用抽象数据类型,比如有一个Integer类的话,我们就可以将主要精力放在程序所需的计算之上,而不必考虑诸如数值范围等这样一些细节问题(Integer类对内建的int或long数据类型进行扩展,以进行数值范围处理等功能)。当Integer类的某个操作导致整数溢出时,Integer类应该提供例外处理以使程序与系统无关。
在C语言或Pascal语言这样的过程语言中也能实现抽象数据类型,不过这些语言对抽象数据类型的支持甚少。相反,通过类的设计,面向对象语言借助信息隐藏和封装对抽象数据类型提供直接支持。像C++(www.cppentry.com)这样的面向对象语言就是创建抽象数据类型的有力工具,同时抽象数据类型又是现代程序设计的首选数据类型。