本节书摘来自异步社区出版社《C++面向对象高效编程(第2版)》一书中的第章,第2.10节,作者: 【美】Kayshav Dattatri,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.10 抽象数据类型—栈的实现
C++面向对象高效编程(第2版)
下面的示例用于说明,在C中一个简单栈的实现。
Stack.h文件——让所有的抽象数据类型用户都可以使用Stack。
typedef Stack* Stackld;
typedef int bool;
struct Stack {
int* data; /* 在栈上存储元素 */
unsigned count; /* 栈上元素的数量 */
int* top; /* 栈顶部的指针 */
/* 略去其他细节 ...*/
};```
Stack.c文件——用于操控Stack的一些函数的实现。
StackId CreateStack()
{
/* 此处代码用于创建一个新的Stack
返回新的栈名(StackId) */
}
bool DestroyStack(StackId whichStack)
{
/ 此处代码用于销毁whichStack /
}
void Push(StackId whichStack, int thisElement)
{
/ 此处代码用于将thisElement 压入whichStack /
}
int Pop(StackId whichStack)
{
/ 此处代码用于从whichStack中弹出顶部元素 /
}`
Stack.c文件与Stack.h文件一同编译后,把产生的目标码和Stack.h文件一起提供给用户。Stack的用户不能访问用于操控栈的函数源代码。
Stack的数据结构只是一段哑数据(dumb data),正确使用Stack所需要的信息都嵌入在Push、Pop等用于支持数据结构的函数中。对用户而言,重要的信息全都在Stack的数据结构中,函数只提供了一个操控数据的工具。但是,用户不使用提供的函数也可访问和修改Stack的数据结构,编译器无法制止用户这样做。换而言之,数据结构和函数之间缺乏紧密耦合。然而,在面向对象编程(OOP)语言中,函数与数据结构形成一个完整的单元(Stack类),必须使用该类提供的函数才能访问对象中的数据。对象拥有数据结构,且只允许接口访问它。由此可见,在支持OOP的语言中更容易实现抽象数据类型。
在面向过程语言中,程序员定义类型与语言定义的新类型非常相似,但前者没有太多保护,任何人都可以直接修改它们。这样的程序员定义类型称为抽象数据类型(abstract data type)。之前我们设计的LD播放机就是一个抽象数据类型。OOP语言的优势在于,它提供数据封装,保护了实现(所以也保护了实现者)。因此,在OOP中,程序员定义类型可被当做语言定义类型。换句话说,OOP语言有特权,也有责任。
举个例子,考虑一个整数数据类型,用于表示非常大的数字(常用于天文学中)。32位整数(4字节整数)无法满足我们的要求,因此需要创建一个新类型,即一个抽象数据类型,TInt。
抽象数据类型——TInt
操作(接口):
加(操作符+)
减(操作符-)
乘(操作符×)
除(操作符/)
求模(操作符%)
增加(操作符+=)
幂
绝对值
打印
…
在该例中,肯定要隐藏TInt类型的实现细节。我们可能使用两个4字节整数(或一个8字节的数组)来实现这个新类型,但是TInt的客户并不知道(也不应该知道)这些细节。如果将来使用支持64位整数(这很有可能)的处理器,就可以直接在实现中使用,不用我们目前的这种表示方法,这些都由实现者决定。
利用数据抽象,我们创建了一个新类型,并且为这个新类型提供了一组有用的操作。因为语言没有直接支持这个类型,所以程序员只好利用抽象实现它,因此它是一个抽象数据类型。鉴于此,数据抽象有时也被定义为:定义抽象数据类型以及一组操作并隐藏实现的过程。