1 前言
栈是一种特殊的线性表,栈仅能在线性表的一端进行操作,栈顶(Top)为允许操作的一端,栈底(Bottom)为不允许操作的一端,栈的特性就是后进先出(Last In First Out),只允许在线性表的一段进行操作
栈可以看成一个容器,所以栈的操作有创建栈,销毁栈,清空栈,进栈(pop),出栈(push),获取栈顶元素,获取栈的大小,对于栈有静态栈和动态栈,所以我们可以抽象出一个**栈父类,静态栈和动态栈继承于该父类,**我们用一片连续空间(原生数组)来容纳栈中元素,还需要设置一个标记变量top,该标记变量指向栈顶,用来标记栈空还是栈满,当栈中没有任何元素的话该top变量指向-1,当栈中装满元素则指向capacity-1的地方
2 StaticStack
对于StaticStack设计要点
1.使用原生数组作为栈的存储空间
2.使用类模板参数决定栈的最大容量
父类代码 : stack.h
#ifndef STACK_H
#define STACK_H
#include "object.h"
namespace CGSLib
{
template <typename T> //使用参数模板
class Stack:public Object
{
public:
virtual void push(const T& e) = 0;//入栈函数
virtual void pop(void) = 0;//出栈函数
virtual T top()const = 0;//获取栈顶的元素
virtual void clear() = 0;//消除栈
virtual int size()const = 0;//获取栈的大小
};
}
#endif // STACK_H
子类代码: staticstack
#ifndef STATICSTACK_H
#define STATICSTACK_H
#include "Stack.h"
#include "Exception.h"
namespace CGSLib
{
template <typename T,int N>
class StaticStack : public Stack<T>
{
protected:
T m_space[N];//连续数组空间来存放
int m_top;//指向栈顶
int m_size;//表示栈内元素的大小
public:
/*默认构造函数,栈顶指向-1,元素为0*/
StaticStack()
{
m_top = -1;
m_size = 0;
}
/*入栈函数*/
void push(const T& e)
{
if(m_size<N)
{
m_space[m_top+1] = e;
m_size++;
m_top++;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"no memery to push");
}
}
/*出栈函数*/
void pop(void)
{
if(m_size>0)
{
m_top--;
m_size--;
}
else
{
THROW_EXCEPTION(InvalidOperationException ,"no element to pop");
}
}
/*获取栈顶元素*/
T top() const
{
return m_space[m_top];
}
/*清除函数*/
void clear()
{
m_top = -1;
m_size = 0;
}
/*返回存有的元素的个数*/
int size() const
{
return m_size;
}
/*返回连续空间的大小*/
int capacity()const
{
return N;
}
};
}
#endif // STATICSTACK_H
测试函数 main.c
using namespace std;
using namespace CGSLib;
int main()
{
StaticStack<int,5> Stack;
for(int i=0;i<5;i++)
{
Stack.push(i);
}
while(Stack.size())
{
cout<<Stack.top()<<endl;
Stack.pop();
}
return 0;
}
测试结果:
4
3
2
1
0
可以看出我们的实验成功了,满足了栈的先进后出的特性
2 LinkStack
在上面我们实现了静态栈,但是对于静态栈他是有缺陷的,当我们存储的元素为类类型时,StaticStack的对象在创建时,会多次调用元素类型的构造函数,影响效率,我们写个例子测试下
#include <iostream>
#include "StaticStack.h"
using namespace std;
using namespace CGSLib;
class Test : public Object
{
public:
Test()
{
cout<<"Test()"<<endl;
}
~Test()
{
cout<<"~Test()"<<endl;
}
};
int main()
{
StaticStack<Test,5> Stack;
cout<<Stack.size()<<endl;
return 0;
}
测试结果:
Test()
Test()
Test()
Test()
Test()
0
~Test()
~Test()
~Test()
~Test()
~Test()
可以看出这个缺陷确实存在的,StaticStack在实现的时候使用了原生数组来实现,这样的话在创建栈对象的时候当然会去调用对应的构造函数,在函数返回时会调用析构函数,所以我们要改进,就要引入链式栈
链式栈的存储实现
链式栈的本质就是链表,定义top指针始终指向链表的首元素,当入栈时就将该元素变为首节点,并将top指针指向它,当出栈时将top指针指向的元素析构,top指针指向下一个节点
链式栈的设计要点
1.使用类模板,抽象父类Stack的直接子类
2.在内部组合使用LinkList类(该类我们在之前实现过),实现栈的链式存储
3.只在单链表成员对象的头部进行操作
LinkList 代码如下
#ifndef LINKSTACK_H
#define LINKSTACK_H
#include "LinkList.h"
#include "Stack.h"
#include "Exception.h"
namespace CGSLib
{
template <typename T>
class LinkStack:public Stack<T>
{
protected:
LinkList<T> m_list; //定义一个LinkList链表类对象
public:
void push(const T& e)
{
m_list.instert(e);//入栈,在链表的头部插入元素
}
void pop(void)
{
if(m_list.length()>0)
{
m_list.remove(0);//出栈,删除链表的头部元素,
}
else
{
THROW_EXCEPTION(InvalidOperationException ,"no element to pop");
}
}
T top() const
{
if(m_list.length()>0)
{
return m_list.get(0); //返回栈顶元素的值,在链表栈中栈顶元素总是首结点的值,也就是我们上面说的top一直指向首节点
}
else
{
THROW_EXCEPTION(InvalidOperationException ,"no element to top");
}
}
void clear()
{
m_list.clear();//调用链表的清除函数
}
int size() const
{
return m_list.length();//返回链表的长度,也就是栈中的元素个数
}
};
}
#endif // LINKSTACK_H
测试代码如下
#include <iostream>
#include "LinkStack.h"
using namespace std;
using namespace CGSLib;
class Test : public Object
{
public:
Test()
{
cout<<"Test()"<<endl;
}
~Test()
{
cout<<"~Test()"<<endl;
}
};
int main()
{
LinkStack<Test> Stack;
cout<<Stack.size()<<endl;
return 0;
}
测试结果:
0
可以看出这样我们在刚开始创建栈的时候不用到原生数组,也就不会调用到对象的构造函数,避免资源的消耗,链式栈的实现组合使用了单链表对象,在单链表的头部进行操作能够实现高效的入栈和出栈操作,栈非常适合于需要"就近匹配"的场合
3.栈的应用实践
符号匹配问题,在C语言中有一些成对匹配出现的符号,比如(),[],{},<>, ’ ’ , " " 等等,那么编译器是怎样去匹配这些符号的呢?算法思路如下
1.
从第一个字符开始扫描
当遇见普通字符时忽略
当遇见左符号时压入栈中
当遇见右符号时弹出栈顶符号,并进行匹配
2.
成功则所有字符扫描完毕,且栈为空
失败则匹配失败或所有字符扫描完毕但是栈非空
#include <iostream>
#include "LinkStack.h"
using namespace std;
using namespace CGSLib;
/*判断是否为左符号的函数*/
bool is_left(char c)
{
return (c == '(')||(c == '{')||(c == '[')||(c == '<');
}
/*判断是否为右符号的函数*/
bool is_right(char c)
{
return (c == ')')||(c == '}')||(c == ']')||(c == '>');
}
/*是否为' '' 的函数*/
bool is_quot(char c)
{
return (c == '\'')||(c == '\""');
}
/*判断是否匹配*/
bool is_match(char l,char r)
{
return ((l == '(')&&(r == ')'))||
((l == '<')&&(r == '>'))||
((l == '{')&&(r == '}'))||
((l == '[')&&(r == ']'))||
((l == '\'')&&(r == '\''))||
((l == '\""')&&(r == '\""'));
}
/*扫描函数*/
bool scan(const char* code)
{
LinkStack<char> stack; /*定义一个链表栈*/
int i = 0;
bool ret = true;
code = (code == NULL)? " " : code;/*判空处理*/
while(ret&&code[i] != '\0')
{
/*如果为左符号则入栈*/
if(is_left(code[i]))
{
stack.push(code[i]);
}
/*右符号处理函数*/
else if(is_right(code[i]))
{
if(stack.size()>0 &&is_match(stack.top(),code[i]))/*如果匹配且链表长度大于0则首结点出栈*/
{
stack.pop();
}
else
{
ret = false;/*否则设置错误*/
}
}
/*' '' 处理函数*/
else if(is_quot(code[i]))
{
if((stack.size() == 0)||!is_match(stack.top(),code[i]))/*如果长度为0,则不匹配则该符号为左符号,入栈*/
{
stack.push(code[i]);
}
else
{
stack.pop();/*否则匹配到则出栈*/
}
}
i++;
}
return ret&&(stack.size()==0);
}
int main()
{
cout<<scan("sasasasads[][][][][]")<<endl;
return 0;
}
测试结果
1