栈本身也是一种线性空间结构,很容易实现向线性地址空间的映射。与队列的头尾两端都可以移动不同(都可以移动,不代表一定要移动),栈只有一个操作结点的端口,通常称为“栈的顶端”。因为栈的顶端总是固定的,所以栈顶随着结点的压入和弹出,其位置总是变化的。
我们爬楼梯模型中,我们假设自己的工作室在N楼:当这一数值为正时,说明我的办公室在空中,随着我们接近办公室,楼层的号码越来越大,我们形象地称为“向上生长”;当这一数值为负时,说明我们的办公室在地下,随着我们接近办公室,楼层的值越来越小,我们称之为向下生长。
数据结构中,栈也有生长方向之分:随着栈的生长,地址的数值越来越大的,称为“向上生长”;随着栈的生长,地址的数值越来越小,称为“向下生长”。这里所谓的生长,就是指随着向栈中“压入新的结点”,栈顶与栈底之间的距离逐渐变大的过程。
两种生长方式看似没区别,实际上却隐含了栈的最大尺寸的信息。设想,如果一个栈采用的是向下生长方式,栈底所在地址的数值就是栈中最大的,栈顶所在的位置,只能越来越小。因为线性地址空间不可能出现负值,也就是说,栈顶所在的位置到“线性存储空间0地址”这段距离就是这个栈所能使用的理论最大空间。与这种限定了最大空间的生长方式不同,采取向上生长策略的栈,从理论上说,栈顶所在的位置可以无限增大,因此不存在理论上的最大限制--栈的生长范围完全取决于存储器或者分配给栈的存储空间的大小。
根据以上分类,我们很容易得出一个典型简单代码:
//向上生长的堆栈
#define STACK_SIZE 10;
#define STACK_BOTTOM 0;
int nStackArray[STACK_SIZE]={0};
unsigned char cStackPoint=STACK_BOTTOM;
//向栈中压入一个数据
BOOL Push(int nData)
{
//判断栈是否已满
if(cStackPoint == STACK_SIZE)
{
return FALSE;
}
nStackArray[cStackPoint]=nData;
cStackPoint++;
return TRUE;
}
//“弹”出栈顶数据
BOOL Pop(int *pData)
{
//判断栈是否已空
if(cStackPoint==StackBottom)
{
return FALSE;
}
*Pdata=nStackArray[nStackPoint-1];//取出数据
cStackPoint--;
return TRUE;
}
以上代码描述的是一个采取向上生长方式的栈。我们注意到,所谓栈的生长方式完全是通过一个名叫cStackPoint的变量来体现的,这个变量通常被称为“栈顶指针”,和前面介绍过的队头指针和队尾指针一样,这也是一个逻辑指针,用来指示栈顶所在的位置。在爬楼梯模型中,爬楼梯的人就是一个具体化得栈顶指针。每当一个新的结点被压入栈中时,栈顶指针是增大还是减小,就直接决定了栈的生长类型。指针以上代码,如果我们想得到一个向下生长的栈,就需要适当对栈顶指针进行一些修改。
//向下生长的栈
#define STACK_BOTTOM 10;
#define STACK_SIZE STACK_BOTTOM;
int nStackArray[STACK_SIZE]={0};
unsigned char cStackPoint=STACK_BOTTOM;
//压入一个数据
BOOL Push(int nData)
{
//判断堆是否已满
if(cStackPoint == 0)
{
return FALSE;
}
nStackArray[cStackPoint-1]=nData;//压入数据
cStackPoint--;//栈向下生长一格
return TRUE;
}
//弹出一个数据
BOOL Pop(int *Pdata)
{
//判断堆是否已空
if(cStackPoint==nSTACK_BOTTOM)
{
return FALSE;
}
//判断指针是否为NULL
if(Pdata==NULL)
{
return FALSE;
}
*Pdata=nStackArray[cStackPoint];//弹出数据
cStackPoint++;//栈指针前移一个
return TRUE;
}