10.7 抽象数据类型
Stock类非常具体。然而,程序员常常通过定义类来表示更通用的概念。例如,就实现计算机专家们所说的抽象数据类型(abstract data type,ADT)而言,使用类是一种非常好的方式。顾名思义,ADT以通用的方式描述数据类型,而没有引入语言或实现细节。例如,通过使用栈,可以以这样的方式存储数据,即总是从堆顶添加或删除数据。例如,C++程序使用栈来管理自动变量。当新的自动变量被生成后,它们被添加到栈项;消亡时,从栈中删除它们。
下面简要地介绍一下栈的特征。首先,栈存储了多个数据项(该特征使得栈成为一个容器——一种更为通用的抽象);其次,栈由可对它执行的操作来描述。
- 可创建空栈。
- 可将数据项添加到栈顶(压入)。
- 可从栈顶删除数据项(弹出)。
- 可查看栈否填满。
- 可查看栈是否为空。
可以将上述描述转换为一个类声明,其中公有成员函数提供了表示栈操作的接口,而私有数据成员负责存储栈数据。类概念非常适合于ADT方法。
私有部分必须表明数据存储的方式。例如,可以使用常规数组、动态分配数组或更高级的数据结构(如链表)。然而,公有接口应隐藏数据表示,而以通用的术语来表达,如创建栈、压入等。程序清单10.10演示了一种方法,它假设系统实现了bool类型。如果您使用的系统没有实现,可以使用int、0和1代替bool,false和true。
程序清单10.10 stack.h
// stack.h -- 用于表示抽象数据类型——栈的类定义
#ifndef STACK_H_
#define STACK_H_
typedef unsigned long Item;
class Stack
{
private:
enum {MAX = 10}; // 常量
Item items[MAX]; // 保存数据项的数组
int top; // 栈顶数据项的索引
public:
Stack();
bool isempty() const;
bool isfull() const;
// 如果栈已满,则push()方法返回falses,否则返回true
bool push(const Item & item); // 在栈中添加数据项
// 如果栈为空,则pop()方法返回false,否则返回true
bool pop(Item & item); // 从栈顶删除数据
};
#endif
在程序清单10.10所示的示例中,私有部分表明,栈是使用数组实现的;而公有部分隐藏了这一点。因此,可以使用动态数组来代替数组,而不会改变类的接口。这意味着修改栈的实现后,不需要重新编写使用栈的程序,而只需重新编译栈代码,并将其与已有的程序代码链接起来即可。
接口是冗余的,因为pop()和push()返回有关栈状态的信息(满或空),而不是void类型。在如何处理超出栈限制或者清空栈方面,这为程序员提供了两种选择。他可以在修改栈前使用isempty()和isfull()来查看,也可以使用push()和pop()的返回值来确定操作是否成功。
这个类不是根据特定的类型来定义栈,而是根据通用的Item类型来描述。在这个例子中,头文件使用typedef用Item代替unsigned long。如果需要double栈或结构类型的栈,则只需修改typedef语句,而类声明和方法定义保持不变。类模板(参见第14章)提供了功能更强大的方法,来将存储的数据类型与类设计隔离开来。
接下来需要实现类方法,程序清单10.11提供了一种可行的实现。
程序清单10.11 stack.cpp
// stack.cpp -- Stack成员函数
#include "stack.h"
Stack::Stack() // 创建一个空栈
{
top = 0;
}
bool Stack::isempty() const
{
return top == 0;
}
bool Stack::isfull() const
{
return top == MAX;
}
bool Stack::push(const Item & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
bool Stack::pop(Item & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
默认构造函数确保所有栈被创建时都为空。pop()和push()的代码确保栈顶被正确处理。这种保证措施是OOP更为可靠的原因之一。假设要创建一个独立数组来表示栈,创建一个独立变量来表示栈顶索引。则每次创建新栈时,都必须确保代码是正确的。没有私有数据提供的保护,则很可能由于无意修改了数据二导致程序出现非常严重的故障。
下面来测试该栈。程序清单10.12模拟了售货员的行为——使用栈的后进先出方式,从购物筐的最上面开始处理购物订单。
程序清单10.12 stacker.cpp
// stacker.cpp -- 测试Stack类
#include <iostream>
#include <cctype> // or ctype.h
#include "stack.h"
int main()
{
using namespace std;
Stack st; // 创建一个空栈
char ch;
unsigned long po;
cout << "Please enter A to add a purchase order,\n"
<< "P to process a PO, or Q to quit.\n";
while (cin >> ch && toupper(ch) != 'Q')
{
while (cin.get() != '\n')
continue;
if (!isalpha(ch))
{
cout << '\a';
continue;
}
switch(ch)
{
case 'A':
case 'a': cout << "Enter a PO number to add: ";
cin >> po;
if (st.isfull())
cout << "stack already full\n";
else
st.push(po);
break;
case 'P':
case 'p': if (st.isempty())
cout << "stack already empty\n";
else {
st.pop(po);
cout << "PO #" << po << " popped\n";
}
break;
}
cout << "Please enter A to add a purchase order,\n"
<< "P to process a PO, or Q to quit.\n";
}
cout << "Bye\n";
return 0;
}
程序清单10.12中的while循环删除输入行中剩余部分,就现在而言这并非是必不可少的,但它使程序的修改更方便(第14章将对这个程序进行修改)。下面是该程序的运行情况:
Please enter A to add a purchase order, P to process a PO, or Q to quit. A Enter a PO number to add; 17885 Please enter A to add a purchase order, P to process PO, or Q to quit. P PO #17885 popped Please enter A to add a purchase order, P to process a PO, or Q to quit. Enter a PO number to add: 17965 Please enter A to add a purchase order, P to process a PO, or Q to quit. A Enter a PO number to add: 18002 Please enter A to add a purchase order, P to process a PO, or Q to quit. P PO #18002 popped Please enter A to add a purchase order, P to process a PO, or Q to quit. P PO #17965 popped Please enter A to add a purchase order, P to process a PO, or Q to quit. P stack already empty Please enter A to add a purchase order, P to process a PO, or Q to quit. Q Bye文件下载(已下载 478 次)
发布时间:2014/7/9 上午9:44:11 阅读次数:4798
