栈的定义:栈是一种特殊的线性表(容器)。
栈仅能在线性表的一端进行操作:
栈顶(top):允许操作的一端;
栈底(bottom):不允许操作的一端。
栈的特性:后进先出(Last in First Out)
栈的操作(函数):创建(stack())、销毁(~stack())、清空(clear())、进栈(push())、出栈(pop())、获取栈顶元素(top())、获取栈的大小(size())。
template < typename T>
class Stack : public Object
{
public:
virtual void push( const T& e) = 0;
virtual void pop() = 0;
virtual T top() const = 0;
virtual void clear() = 0;
virtual int size() const = 0;
};
一、StaticStack顺序栈设计要点:
类模板:使用原生数组作为栈的存储空间
使用模板参数(<T, N>)决定栈的最大容量
代码实现如下:
template <typename T, int N>
class StaticStack : public Stack<T>
{
protected:
T m_space[N]; //栈存储空间,N为模板参数
//此处已经涉及到泛指类型进行具体对象的构造了,所以效率不高。
int m_top; //栈顶标志
int m_size; //当前栈的大小
public:
StaticStack()
{
m_top = -1;
m_size = 0;
}
int capacity() const
{
return N;
}
void push(const T& e)
{
if( m_size < N)
{
m_space[m_top + 1] = e; //为了异常安全,在复制操作符出现异常的情况下,栈内部的数据并不会出现改变
m_top++; //e可能是一个类类型,如果真的这个类类型未进行复制操作符重载,抛出异常前不对m_top做任何改变
m_size++;
}
else
{
//抛出异常
}
}
void pop()
{
if( m_size > 0)
{
m_top--;
m_size--;
}
else
{
//抛出异常
}
}
T top() const { if( m_size > 0) { return m_space[m_top]; //直接返回栈顶的值 } else { //抛出异常 } } void clear() { m_top = -1; m_size = 0; } int size() const { return m_size; } };
在主函数中测试如下代码:
int main()
{
StaticStack<int, 5> ss;
for(int i=0; i<5; i++)
{
ss.push(i);
}
while (ss.size() > 0)
{
cout << ss.top();
ss.pop();
}
return 0;
}
输出效果为:
小结:栈只允许在线性表的一端进行操作。
注意:StaticStack使用原生数组作为内部存储空间
StaticStack的最大容量由模板参数决定。
二、(LinkStack)链式栈的实现
问题提出:当存储的元素为类类型时,StaticStack的对象在创建时,会多次调用元素类型的构造函数,影响效率。原因在于使用原生数组作为存储空间,在创建栈对象的时候,会调用泛指类型的构造函数。
链式栈的设计要点(本质就是链表):
1、类模板,抽象父类Stack的直接子类;
2、在内部组合使用LinkList类,实现栈的链式存储;
3、只在单链表成员对象的头部进行操作。
实现代码如下:
template <typename T>
class LinkStack : public Stack <T>
{
protected:
LinkList<T> m_list;
public:
void push(const T& e) //O(1) 注:此处为头插法
{
m_list.insert(0,e);
}
void pop() //O(1 )
{
if( m_list.length() > 0)
{
m_list.remove(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException,"No element in current stack...");
}
}
T top() const
{
if( m_list.length() > 0)
{
return m_list.get(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException,"No element in current stack...");
}
}
void clear() //O(n)
{
m_list.clear();
}
int size() const //O(1)
{
return m_list.length();
}
};
在主函数中编写如下代码:
class Test : public Object
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main()
{
LinkStack<Test> ls;
cout << ls.size() << endl;
return 0;
}
运行后发现,构造函数和析构函数均未执行,效率提升。
栈的应用之一:符号匹配问题:
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()) && is_match(stack.top(), code[i]))
{
stack.pop();
}
else
{
ret = false;
}
}
else if( is_quot(code[i]))
{
if( stack.size() == 0 || !is_match(stack.top(), code[i]) ) //不匹配的时候
{
stack.push(code[i]);
}
else if(is_match(stack.top(), code[i]))
{
stack.pop();
}
}
i++;
}
return ret && (stack.size() == 0);
}
小结:链式栈的实现组合使用了单链表对象(又是不同的数据结构)。
在单链表的头部进行操作能够高效的入栈和出栈操作。
栈“后进后出”的特性适用于检测成对出现的符号。
栈非常适合于需要“就近匹配”的场合。