上两篇文章介绍了两个线性存储结构,数组和链表。它们各有各的优点和缺点,那就要看在什么场合来使用他们。如果使用得当,会助你一臂之力。
承蒙朋友的关心和抬爱,多对上两篇文章做了评论。好多人觉得写得太长了,懒得看下去。我本人觉得不然,空洞的东西和理论在教科书中已经写得够多的了,需要写一些实际的东西。在学校时,很多时候是那着书本不知道如何下手。其实这个时候也许理论知识掌握得很熟了,默写大概没有什么问题。要是不用写代码演示的话,差不多可以去给低年级的学第、学妹讲课了(夸张了)。那个时候很想找写代码来看,但是条件不允许啊。现在工作了,觉得写些简单的东西给大家看还是没有什么问题的。也许不是很完美,会有很多漏洞,但是我想还是能起到“领会精神”的效果吧。代码所以多,因为封装的方法多,而且还用到了些不常用的东西,这样在广度上也是个提高。
我写的这些代码,在VC6.0上调试通过,其中的函数我也作单步调试了。大家可以把这些代码复制下来,加上#include <stdexcept>就可以开始调试了。
对留贴的朋友做了些回复后要写正题了。这篇文章介绍一下栈。
教科书上说,栈就像枪梭子,最后上的子弹先出去。这已经很形象了,也很好理解。说得再明白些,栈就是只在栈顶进行数据项添加和删除的操作的容器。所以,栈是一种“后进先出”(LIFO)的数据结构。那么,栈的功能又有哪些呢?引用一段话吧“现在假设老板过来要求你立即填写一份表单。你停下手中的工作,把表单放在一堆纸上。当你填完表单的时候,就把它从栈顶(纸堆)上拿走,又回到刚才中断的工作上去”。从这个例子可以看出,栈能记录已部分完成的工作。
说了基本的概念,这很好理解。我想还是结合实际,更有助于理解。比如,当程序中调用一个方法时,调用指令后的第一条指令的地址压入当前线程的方法调用栈的顶端。当执行被调用方法的返回指令时,该地址从栈顶弹出,然后从该地址处继续执行。
在众多容器中最简单的数据结构就是栈。它只提供Push和Pop操作。前者是压入,后者是弹出。除了这两种操作外,典型的栈操作还提供一个Top的操作。它总是返回栈顶的数据项,而不将其删除。
首先,创建一个Stack的基类。这个类中定义Push,Pop和Top三个纯虚函数。用这种多态的方法可以便于我们分别用数组和链表的方法来实现栈这个数据结构。
template <class T>
class Stack
{
public:
virtual T& Top() const = 0;
virtual void Push(T&) = 0;
virtual T Pop() = 0;
protected:
int nLength;
int nCount;
};
用链表实现Stack的方法如下:
template <class T>
class StackList : public Stack<T>
{
public:
StackList(int nCnt)
{
nCount = 0;
nLength = nCnt;
plist = new LinkedList<T>();
}
~StackList()
{
delete plist;
}
T &Top() const
{
if(0 == nConut)
{
throw domain_error( "Stack is empty!" );
}
return const_cast<T&> (plist->GetFirst()->GetDatum());
}
void Push(T &Value)
{
if(nCountt > nLength)
{
throw domain_error( "Stack is full!" );
}
plist->Prepend(Value);
++nCount;
}
T Pop()
{
if(0 == nConut)
{
throw domain_error( "Stack is empty!" );
}
T tResult = plist->GetFirst()->GetDatum();
plist->RemoveAt(tResult);
--nConut;
return tResult;
}
private:
LinkedList<T> *plist;
// int nCount;
// int nLength;
};
用数组实现Stack的方法如下:
template <class T>
class StackArray:public Stack<T>
{
public:
StackArray(int nCnt)
{
pos = 0;
nLength = nCnt;
nCount = 0;
pArray = new Array<T>(nCnt);
}
~StackArray()
{
if(NULL != pArray)
{
delete pArray;
}
}
T &Top() const
{
if(0 == nCount)
{
throw domain_error( "Stack is empty!" );
}
return (*pArray)[pos - 1];
}
void Push(T &value)
{
if(pos == nLength)
{
throw domain_error( "Stack is full!" );
}
(*pArray)[pos] = value;
++nCount;
++pos;
}
T Pop()
{
if(0 == nCount)
{
throw domain_error( "Stack is empty!" );
}
int n = --pos;
--nCount;
return (*pArray)[n];
}
private:
Array<T> *pArray;
// int nLength;
int pos;
// int nCount;
};
我在StackArray类和StackList类中没封装有默认构造函数,都只有一个含参构造函数。这个参数的作用就是要初始化栈的大小。因为在现实中栈的大小是有限的,如果无限制的压栈,那么将会栈资源耗尽。