7.8 数据抽象与信息隐藏
类通常对类的客户隐藏其实现细节,即所谓的信息隐藏。下列以堆栈数据结构作为信息隐藏的例子。
可以把堆栈看成一堆盘子。将盘子放在堆中时,总是放在顶部(压入堆栈),从堆中取下盘子时,总是从顶上取(称为弹出堆栈)。堆栈是后进先出(last-in,first-out;LIFO)的数据结构,最后放进堆栈的项目最先从堆栈中取出。
程序员可以生成堆栈类,对客户隐藏实现细节。堆栈可以方便地用数组实现(或用第15章“数据结构”中的链表)。堆栈类的客户不需要知道堆栈如何实现,只要求数据项目按后进先出的方式处理即可。
描述类的功能而不管其实现细节称为数据抽象(data abstraction),C++ 的类定义了抽象数据类型(abstract data types,ADT)。尽管用户可能知道类的实现细节,但编码时不能依赖于这些实现细节。只要该类的 public 接口不变,类的实现细节(如堆栈的实现和 压入 弹出 的操作)可能改变而不影响系统其余部分。
高级语言的任务是生成便于程序员使用的视图。由于没有一个标准视图,因此编程语言种类很多。C++ 中的面向对象编程显示了另一种视图。
大多数编程语言都强调操作。在这些语言中,数据的存在只是为了支持程序所需的操作。数据比操作更不重要,数据是原始的,只有几个内部数据类型,程序员很难生成自己的新数据类型。
C++ 和面向对象编程中改变了这些观点。C++ 提高了数据的重要性。C++ 中的主要活动就是生成自己的新数据类型(即类)和表达这些数据类型之间的相互作用。
要转向这个方向,编程语言组织要规范数据的一些概念。我们考虑的规范化就是抽象数据类型。抽象数据类型就像十多年前的结构化编程一样备受关注,抽象数据类型并不能代替结构化编程,只是提供了其他规范,可以进一步改善程序开发过程。
什么是抽象数据类型呢?考虑内部类型int,我们看到它是数学中的整数,但int在计算机中并不完全是数学中的整数,计算机中的 int 长度是很有限的。例如,32位机器上的int只限于-20亿到20亿之间。如果计算结果超出这个范围,则会发生溢出错误,机器会以机器相关的方式响应,可能悄悄地产生错误结果,而数学中的整数则没有这个问题。因此,计算机中的int概念实际上只是实际整数的一个近似,float也是这样。
char 同样是个近似,char值通常是8位模式的0和1,这些模式与所表示的字符(如z、小写z、美元号 $、数字 5 等等)完全不同。char 类型的值在大多数计算机上都是很有限的。7位ASCII字符集只提供128个不同字符值。这显然不足以表达中文、日文等需要成千上万个字符的语言。
由此可见.即使 C++ 之类的编程语言提供的内部数据类型,实际上也只是实际生活中概念和行为的近似或模型。前面我们一直理所当然地使用int,现在则有了全新的概念。int、float、char 之类的类型都是抽象数据类型,实际上是在计算机系统中一定程度地近似表示实际中的概念。
抽象数据类型实际上包含两个概念,即数据表达(data representation)和该数据允许的操作(operation)。例如,int 的概念定义了 C++中的加、减、乘、除、求模操作,但除数为0时则未定义,这种操作与机器参数有关,如计算机系统的定长字长度。另一个例子是负整数的概念,其运算和数据表达是可应用的,但负整数的平方根则没有定义。C++ 中程序员用类实现抽象数据类型。我们将在第]2章“模板”中生成自己的堆栈类,并在第20章 标准模板库(STL) 中介绍标准库 stack 类。
7.8.1 范例:数组抽象数据类型
第4章曾介绍过数组。数组就是一个指针和一些内存空间。如果程序员小心谨慎,则利用这些原始功能就可以进行数组操作。数组还有许多精彩的操作,但C++中没有提供。利用 C++ 类,程序员可以开发比“原始”数组更精彩的数组ADT。数组类可以提供许多有用的功能,例如:
- 下标范围检查。
- 任章范围下标而不一定从0开始。
- 数组赋值。
- 数组比较。
- 数组输入/输出。
- 已知长度数组。
我们将在第8章 运算符重载 中生成自己的数组类,并在第20章“标准模板库(STL)”中介绍标准库vector类。
C++有少量内部类型,类可以扩展这个基础编程语言。
软件工程视点 7.15
程序员可以通过类机制生成新类型。这些新类型可以方便地像内部类型一样使用。这样,C++是个可扩展的语言。尽管这个语言可以方便地用新类型扩展,但基础语言本身不能改变。
C++ 环境中生成的新类可以专属一个人、一个小组或一个公司。类也可以放进标准类库中,以便推广使用。这不一定能上升为标准,但事实上标准正在出现。C++的全部价值只有在充实而标准化的类库得到广泛应用时才能完全体现。在美国,ANSI(美国国家标准协会)正在进行这种标准化。ANSI 和 ISO(国际标准化组织)正在开发C++的标准版本。学习C++和面向对象编程的读者可以利用丰富的库实现快速的面向组件的软件开发。
7.8.2 范例:字符串抽象数据类型
C++ 是一种定义简练的语言,只向程序虽提供了建立各种系统的原始功能。这个语言保证了最小编程负担。C++ 适合应用编程和系统编程,后者对程序的性能有更高要求。当然,C++ 内部数据类型中也可以包括字符串数据类型,但这个语言设计成包括通过类生成和实现字符串抽象数据类型的机制。
我们将在第8章开发自己的字符串 ADT。ANSI/ISO 草案标准中有一个由 string 类,将在第19章详细介绍。
7.8.3 范例:队列抽象数据类型
我们经常遇到排队,等待的队称为队列(queue)。我们排队在超市结账,排队换煤气,排队上公共汽车,排队在高速公路上交费,学生注册时和到食堂买饭时都要排队。计算机系统内部也使用许多排队,因此,我们需要编写一个模拟排队的程序。
队列是个抽象数据类型的范例。队列向客户提供了明确的行为。客户一次一件地将东西放进队列中,称为进队(enqueue),然后从队列中一次一件地取出东西,称为出队(dequeue)。理论上,队列可以无限长,但真正的队列是有限的。队列中的项目按先进先出(first-in.first-out;FIFO)顺序出列,第一个插入队列的项目第一个离开队列。
队列隐藏了内部数据表示,跟踪队列中的当前项目,为客户提供一组操作,如入队和出队。客户并不关心队列的实现,只要求队列正常操作。客户让一个新项目入队时,队列应接受这个项目,并放在某种先进先出的数据结构中。客户要从队列中取一个项目时,应从内部表示中删除这个项目,并将该项目按先进先出的顺序传递给外界(即队列的客户),下一次出队的项目应是队列中等待时间最长的项目。
队列ADT保证内部数据结构的完整性。客户不能直接操作这个数据结构,只有队列ADT能访问其内部数据。客户只能对数据表达进行允许的操作,ADT的public接口中不提供的操作将由 ADT 以适当方式拒绝,例如发一个错误消息、终止执行或忽略操作请求。
第15章 数据结构 中将建立自己的队列类,第20章将介绍标准库 queue 类。