实验2 堆栈ADT文档格式.docx
《实验2 堆栈ADT文档格式.docx》由会员分享,可在线阅读,更多相关《实验2 堆栈ADT文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
classDT>
必须正确地在类声明之前和每一个类成员函数之前。
请记住,DT是我们用来代表模板类中任一数据类型的任意标识符。
语句
classStack
{
public;
…
可以修改成
template<
classStack
{
Public;
…
一个函数定义的开始原来是
Stack:
:
Stack(intmaxNumber)throw(bad_alloc)
现在成为
Template<
Stack<
DT>
现在类名的每一次使用必须包括被尖括号括起来的一般类型的数据类型名。
每一个字符串“Stack”的使用都变成了字符串“Stack<
”。
在构造函数定义的例子中,类标识由“Stack:
”变成“Stack<
还请注意,一个例外情况是构造函数名没有被修改,还是“Stack”。
Template<
Stack<
●在类声明和类定义文件中出现的数据类型名应被选择用来代表一般类型的字符串代替。
例如,语句
int*dataItems;
//Arraycontainingthestackdataitems
//(integers)
成为
DT*dataItems;
//Arraycontainingthestackdataitems
//(generic)
●当实例化类的一个对象时,真实的数据类型(在尖括号里)附在类名之后。
//Aseparatestackimplementationjustforintegers
IntStacksamples(10);
//Datatypespecifiedelsewherebyusingtypedef
Stackline(80);
//Wetellthecompilertomakeacopyofthegenericstackjust
//forintegersandtomakeanotherjustforcharacters.
Stack<
int>
sample(10);
char>
line(80);
●实现文件中的代码(classname.cpp文件中)提供了一系列ADT类实现的模板(或框架)。
在这个框架中数据项的类型故意地不指定,直到类的一个对象被实例化时再指定。
因此,编译器在遇到一列声明时必须访问.cpp文件中的代码,以便根据声明的数据项的类型创建一个实现。
复杂的程序开发环境提供了一系列的机制确保访问代码。
遗憾的是,这些机制还不是标准的交叉系统。
在本书中我们可以使用一种简单而有效的机制,在一个使用了类的程序中,应该包括实现文件(classname.cpp文件)而不是头文件classname.h。
这种方法违反了我们从来不应该使用#include语句直接包含代码的规则。
然而,在大多数的系统中,没有提供其他使用模板类的方法,这些方法把一个程序的所有代码放在一个文件中(一个更差的方法)。
堆栈ADT的一个部分的模板类声明显示如下(完整的声明在试验前练习中给出)。
template<
classDT>
classStack
{
public:
…
Stack(intmaxNumber=defMaxStackSize)throw(bad_alloc);
voidpush(constDT&
newDataItem)throw(logic_error);
DTpop()throw(logic_error);
private:
Dt*dataItems;
//Arraycontainingthestackdataitems
};
请注意在类声明中出现的模板参数DT。
这个参数用来标记明确引用堆栈数据项类型的位置。
堆栈ADT
●数据项
一个堆栈中数据项的数据类型是一般类型DT。
●结构
堆栈中数据项是从最后入栈(栈顶)到最先入栈(栈底)线性排列的。
数据项在栈顶插入(pushed)或删除(popped)。
●运算
Stack(intmaxNumber=defMaxStacksize)throw(bad_alloc)
要求:
无
结果:
构造函数。
创建一个空的堆栈。
为一个包含maxNumber个数据项的堆栈分配足够的内存(如有必要)。
~Stack()
解析函数。
释放(free)存储一个堆栈的内存。
voidPush(constDT&
newDataItem)throw(logic_error)
堆栈非满。
把newDataItem插人到栈顶。
DTpop()throw(logic_error)
堆栈非空。
删除最后加入到栈顶(top)的数据项并且返回。
voidclear()
删除堆栈中所有数据项。
boolisEmpty()const
如果堆栈为空,则返回true,否则,返回false。
boolisFull()const
如果栈满,则返回true,否则,返回false。
voidshowStructure()const
输出堆栈中的数据项。
如果堆栈为空,输出“Emptystack”。
请
注意,这个运算只用于测试/调试目的,仅仅支持数据项是C++预先定义的一种数据类型(int,char,等等)的堆栈数据项。
实验2作业单
姓名小组日期
请在教师布置的练习对应的已布置列上打一个钩(√)。
在提交这个实验的一组材料前面附上这个作业单。
练习
已布置:
打钩或
列出练习编号
已完成
实验前练习
过渡练习
实验中练习1
实验中练习2
实验中练习3
实验后练习1
实验后练习2
总计
实验2实验前练习
如果一个ADT要在各种操作环境中都有效地执行,这个ADT的多重实现是必要的。
依赖于硬件和应用程序,我们可能希望一个实现减少一些(或全部)ADT运算执行时间,或者我们可能希望一个实现减少存储ADT数据项的内存数量。
在这个实验中,我们开发两种堆栈ADT的实现。
一种实现在一个数组中存储堆栈,另一种实现单独存储每个数据项并把这些数据项链接起来构成一个堆栈。
第一步:
使用一个数组存储堆栈数据项,来实现堆栈ADT中的运算。
堆栈大小可变,因此,我们需要存储堆栈中可以存储数据项的最大数目(maxSize),最顶端数据项的数组索引(top),以及堆栈数据项本身(dataItems)。
基于文件stackarr.h中的声明,在文件show5.cpp中给出了showStructure运算的一个实现。
constintdefMaxStackSize=10;
//Defaultmaximumstacksize
{
public:
//Constructor
Stack(intmaxNumber=defMaxStackSize)throw(bad_alloc);
//Destructor
~Stack();
//Stackmanipulationoperations
voidpush(constDT&
newDataItem)throw(logic_error);
DTpop()throw(logic_error);
voidclear();
//Stackstatusoperations
boolisEmpty()const;
//Stackisempty
boolisFull()const;
//Stackisfull
//Outputthestackstructure--usedintesting/debugging
voidshowStructure()const;
//Datamembers
intmaxSize,//Maximumnumberofdataitemsinthestack
top;
//Indexofthetopdataitems
DT*dataItems;
};
第二步:
在文件stackarr.cpp中保存堆栈ADT的数组实现,并确认形成代码文档。
在堆栈ADT的数组实现中,当声明(构造)堆栈时,我们需要为堆栈分配内存。
分配的数组必须足够大,以便满足在一个特定应用程序中可能用到的最大的堆栈。
遗憾的是,大部分时间内,不会用到最大的堆栈,额外的内存没有使用。
一个改进的方法是,随着把新数据项加人到堆栈中,按数据项分配内存。
用这种方法,只有当我们确实需要时,才分配内存。
然而,由于随时在分配内存,所以,数据项占据一段不连续的内存。
其结果是,我们需要把这些数据项链接在一起,构成堆栈的链表表示。
创建一个堆栈ADT的链表实现与开发一个数组实现相比较,编程任务更具有挑战性。
简化这个任务的一种方法是把这个实现分为两个模板化的类:
一个类集中在整个堆栈结构(类Stack)上,另一个类集中在链表的单个结点(类StackNode)上。
从类StackNode开始。
链表的每一个结点包括一个堆栈数据项和一个指向链表下一个数据项节点的指针。
类StackN0de提供的仅有一个函数是创建一个指定节点的构造函数。
对类StackNode的访向只限于Stack类的成员函数。
通过把所有的StackNode的成员声明为私有的,可以阻止别的类直接指向链表节点,通过把Stack类声明为StackNode类的友元(friend)可以使StackNode类的成员成为Stack类可访问的。
这些属性反映在下面的类声明中,这些类声明在文件stacklnk.h中。
Template<
classStack;
classStackNode
private:
//Constructor
StackNode(constDT&
nodeData,StackNode*nextptr);
//Datamembers
DTdataltem;
//Stackdataitem
StackNode*next;
//Pointertothenextdataitem
friendclassStack<
;
请注意上面StackNode声明中的头两行。
这种前向式声明是C++用来解决典型的编译难题。
在StackNode语句中引用Stack。
FriendclassStack<
但是编译器没有遇到Stack类,通常会发出一个指向一个未知数据类型的出错消息。
我们可以把Stack的声明移到StackNode的声明之前,这样就可以确保编译器在指向StackNode之前已经看到Stack。
问题马上又出现了,在StackNode的声明之前Stack的声明中包含对StackNode的引用。
这是一个令人左右为难的规定,因为我们无法让两者在程序中同时首先出现。
C++通过允许一个数据类型在真正声明之前宣称它的存在来解决这个问题。
编译器注意到它会在后面继续声明。
这与我们在遇到一个函数定义之前而在程序的开始介绍这个函数的原型是类似的。
类StackNode的构造函数用于向堆栈增加节点。
例如,下面的语句向一个字符堆栈中增加一个包含‘d’的节点。
请注意,模板参数DT必须是char类型,top是StackNode类型。
top=newStackNode<
(‘d’,top);
new运算为链表节点分配内存,并且调用类StackNode的构造函数,传递一个插人的数据项(‘d’)和一个指向链表下一个节点的指针(top)。
最后,赋值运算符把top指针赋给新分配的节点,从而完成节点的创建和连接。
类Stack的成员函数也实现了堆栈ADT中的运算。
一个指针始终指向链表的开始节点,或者,也可以说是堆栈的顶部。
文件stacklnk.h中给出了如下所示的类Stack的声明。
tesplate<
Stack(intignored=0);
//Destructor
~Stack();
//Stackmanipulationoperations
newDataItem)throw(bad_alloc);
DTpop()throw(logic_error);
voidclear();
//Clearstack
//Stackstatusoperations
boolisEmpty()const;
//Isstackempty?
boolisFull()const;
//Isstackfull?
//Outputthestackstructure--usedintesting/debugging
voidshowStructure()const;
//Datamember
StackNode<
*top;
//Pointertothetopdataitem
第三步:
通过使用一个单链表存储堆栈数据项,实现堆栈ADT中的运算。
链表的每一个节点应该包括一个堆栈数据项(dataItem)和一个指向堆栈中下一个数据项节点的指针。
我们的实现还应该包含一个指向堆栈的最顶部数据项节点的指针(top)。
我们的实现基于文件stacklnk.h中类的声明,在文件show5.cpp中给出了showStructure运算的一个实现。
第四步:
把堆栈ADT的链表实现保存在文件stacklnk.cpp中,确认形成代码文档。
实验2过渡练习
文件test2.cpp中的测试程序允许我们使用下面的命令交互式地测试堆栈ADT的实现。
命令
操作
+x
压入数据项x到栈顶
-
弹出栈顶数据项并输出
E
报告堆栈是否为空
F
报告堆栈是否已满
C
清空堆栈
Q
退出测试程序
编译并链接测试程序。
注意到编译这个程序将会把堆栈ADT的数组实现(在文件stackarr.cpp中)编译为一个字符堆栈的数组实现。
增加测试项目完成下面的测试计划
从一个仅包含一个数据项的堆栈中弹出一个数据项。
向一个经过一系列弹出运算已为空的堆栈中压人一个数据项。
从一个已满的堆栈中弹出一个数据项(数组实现)。
清空堆栈。
执行测试程序,如果在堆栈ADT的数组实现中发现了错误,改正这些错误并且重新执行测试计划。
修改测试程序,使文件stackarr.cpp中的堆栈ADT的链表实现代替数组实现。
第五步:
重新编译并链接测试程序。
注意到编译这个程序将会把堆栈ADT链表实现(在文件stackarr.cpp中)编译为一个字符堆栈的链表实现。
第六步:
使用测试程序检查堆栈ADT的链表实现,如果在堆栈ADT的链表实现中发现了错误,改正这些错误并且重新执行测试计划。
堆栈ADT中运算的测试计划
测试项目
预期结果
检查
一系列压入运算
一系列弹出运算
多压入运算
多弹出运算
栈空?
栈满?
+a+b+c+d
---
+e+f
--
EF
abcd
a
aef
falsefalse
空堆栈
truefalse
注:
栈顶数据项以黑体显示
实验2实验中练习1
姓名小组日期
我们一般使用中缀形式书写算术表达式,即每一个运算符在它的运算数中间,如下面的表达式:
(3+4)*(5/2)
虽然我们习惯于表达式中缀形式,但是,中缀形式有一个缺点,必须使用圆括号指定运算顺序,这些圆括号极大地增加了运算过程的复杂性。
如果我们简单地从左到右对运算数进行运算,那么运算会简单的多。
遗憾的是,这种从左到右的运算策略不能应用在中缀形式的算术表达式上。
不过,可以应用在后缀形式的表达式上。
在后缀形式的算术表达式中,每一个运算符紧跟在它的运算数之后,上面的表达式的后缀形式可以写成:
34+52/*
注意到在这两种形式中,运算数的次序是相同的(从左到右地读),而运算符的次序不同,但是,后缀形式的运算符的次序和他们运算的次序是相同的,一开始后缀形式的表达式的结果难以读取,但是容易运算。
我们所需要做的就是建立一个堆栈存放中间结果。
假设我们有一个后缀形式的算术表达式,包括单数字的非负整数和四个基本的算术运算符(加,减,乘和除)。
这个表达式可以通过下面的与一个浮点数字堆栈相连结的运算法则进行运算。
⏹逐字符地读表达式,每一个字符被读取时,做以下工作。
⏹如果字符是一个单数字的(字符‘0'
到‘9'
),则把相应的浮点数字存人堆栈中。
⏹如果字符是一个算术运算符(字符‘+’,‘-’,‘*’,‘/’),那么从堆栈中弹出一个数字,称为operand1。
⏹从堆栈中弹出一个数字,称为operand2。
⏹使用算术运算符计算这两个运算数,如下
Result=operand2运算符operand1
⏹把result压入堆栈。
当表达式结束时,从堆栈中弹出剩下的数字,这个数字就是表达式的值。
在下面的算术表达式上应用这个运算法,则
34+52/*
产生下面的计算:
‘3’:
压入3.0
‘4’:
压入4.0
‘+’:
弹出,operand1=4.0
弹出,operand2=3.0
计算,result=3.0+4.0=7.0
压入7.0
‘5’:
压入5.0
‘2’:
压入2.0
‘/’:
弹出,operand1=2.0
弹出,operand2=5.0
计算,result=5.0/2.0=2.5
压入2.5
‘*’:
弹出,operand1=2.5
弹出,operand2=7.0
计算,result=7.0*2.5=17.5
压入17.5
‘\n’:
弹出,表达式的值=17.5
创建一个应用程序,该程序可以读取一个后缀形式的算术表达式,进行运算,并且显示结果。
假设这个后缀形式的算术表达式,包括单数字的、非负的整数(‘0’到‘9’,)和四个基本的算术运算符(‘+’,‘-’,‘/’,和‘/’)。
还可以假设算术表达式从键盘输人并且所有的字符都在一行,把程序保存在postfix.cpp文件中。
填充每一个算术表达式的预期结果,完成下面的测试计划。
我们可能想在这个测试计划中增加一些算术表达式。
执行测试计划。
如果发现程序中有错误,改正这些错误并且重新执行测试计划。
后缀形式的算术表达式的运算程序测试计划
算术表达式
一个运算符
嵌套运算
不对称运算
所有运算符在最后
零除
单个数字
34+
34+52/*
3