栈的理论讲解
stack是C++STL容器库中的一员,它的特点是:先入后出,后入先出。这里有一张原理图:
这里说几个专有名词:
- 栈顶:也就是top()所访问的元素,即:栈中最后push(推入)的元素
- 栈底:栈的底层数组。在C++STL的stack中,我们能够自己指定栈底为vector、list或者其他,默认是deque。
我们常用的函数有:
方法 | 作用 |
---|---|
top() | 访问栈顶元素,也仅仅是访问操作 |
pop() | 删除栈顶元素 |
empty() | 检查栈中是否为空 |
size() | 返回栈中所存储的元素个数 |
push() | 向栈中推入元素 |
栈它有个较大的缺陷:它的效率不高,它是对已有的STL进行一个包装。
接下来我们会将这些函数的功能一一实现,并且使用模板,尽可能去模仿STL中的stack。我们所实现的stack为顺序栈,这种栈的栈底是数组;还有一种栈为链式栈,它的栈底是链表。
整体代码
这里先给出整体代码,然后再按照功能逐个分析:
#ifndef STACK_CPP
#define STACK_CPP
#define MINSIZE 3//最小规模
/*
此种Stack是基于数组实现的
真正的stack的底层可以指定各种容器
如vector、list、deque
*/
template<class T>
class Stack{
public:
Stack();
~Stack();
void pop();//出栈
T top();//访问栈顶
void push(const T& value);//添加元素
int size();//返回元素的个数
bool empty();//查看栈是否为空
// void shrink_to_fit();//调整规模
protected:
int sizeVal = 0;//元素个数
int scale = MINSIZE;//规模
T* arr;//存放数据
};
template<class T>
Stack<T>::Stack(){
this->arr = new T[scale];
}
template<class T>
Stack<T>::~Stack(){
delete[] arr;
}
template<class T>
void Stack<T>::pop(){
//缩减规模
sizeVal--;//这也反映了stack使用pop()之后,栈顶元素并不是真正被删除了,只是我们访问不到了
}
template<class T>
inline T Stack<T>::top(){
return arr[sizeVal-1];
}
template<class T>
void Stack<T>::push(const T& value){
//更改规模
//检查规模是否需要扩容
this->sizeVal++;
//需要扩容
if(this->sizeVal > this->scale){
this->scale *= 1.5;
T* newArr = new T[this->scale];
for(int temp=0; temp<sizeVal-1; temp++){
newArr[temp] = this->arr[temp];
}
newArr[sizeVal-1] = value;
delete[] this->arr;
arr = newArr;
}
//不需要扩容
arr[sizeVal-1] = value;
}
template<class T>
inline int Stack<T>::size(){
return this->sizeVal;
}
template<class T>
inline bool Stack<T>::empty(){
//三元表达式
return this->sizeVal == 0 ? true : false;
}
#endif
stack的实现
大部分的函数实现还是很简单的,因此我这里只说我觉得有必要说的。
(1) stack类的声明
先给代码再来分析:
template<class T>
class Stack{
public:
Stack();
~Stack();
void pop();//出栈
T top();//访问栈顶
void push(const T& value);//添加元素
int size();//返回元素的个数
bool empty();//查看栈是否为空
// void shrink_to_fit();//调整规模
protected:
int sizeVal = 0;//元素个数
int scale = MINSIZE;//规模
T* arr;//存放数据
};
我们将分析的重点放在protected权限中:
- 首先第一个问题:我们为什么使用protected而不是private?C++中STL的设计理念中有这么一条:可拓展,而要实现它的可拓展就需要将其私有部分设置为protected。
- scale是什么?在上面代码的注释中其实已经说的很清楚了:stack的规模,也就是它当前状态下所能够存储的最大元素个数。
和先前的vector类似,stack也有可拓展的特性,因此我们需要根据所存放的元素个数的变化不断更改其规模,以达到目的。
(2) push()
push()负责将元素推入栈,是stack中最复杂、代码量最多的函数,但是实际上也不过如此:
template<class T>
void Stack<T>::push(const T& value){
//更改规模
//检查规模是否需要扩容
this->sizeVal++;
//需要扩容
if(this->sizeVal > this->scale){
this->scale *= 1.5;
T* newArr = new T[this->scale];
for(int temp=0; temp<sizeVal-1; temp++){
newArr[temp] = this->arr[temp];
}
newArr[sizeVal-1] = value;
delete[] this->arr;
arr = newArr;
}
//不需要扩容
arr[sizeVal-1] = value;
}
之前说到stack和先前的vector类似,也有自动扩容的特点,因此我们在添加元素的时候就需要考虑:**在添加元素之后,元素的个数是否超过了stack的规模?**若是超过了,就应当扩容,没超过直接添加元素就好。
这里我选择的扩容模式为扩容至原先的1.5倍。同时,为了避免浅拷贝,需要在堆中重新分配数组,也就是这里的newArr。
这里我偷了个懒,stack的效率没有达到最高,想要效率最高可以在for循环中对元素的拷贝操作更改为对元素的转发操作,也就是使用std::move(),反正在此后原数组的空间就被释放了,不需要再次访问,也就没有访问未定义的元素的可能。大家可以根据自己的需要去优化代码。
(3) pop()
pop()的作用是删除栈顶元素,但是想来想去:**好像在数组中没有删除元素的操作吧?**是的没错,在数组中确实没有删除元素的操作,因此我们不难得知:pop()所谓的删除元素并不是真正的删除,至少在顺序表中是这样,只是说在使用pop()之后,栈顶的位置变了,导致我们无法再访问到这个元素。
template<class T>
void Stack<T>::pop(){
//缩减元素个数
//(伪删除)
sizeVal--;//这也反映了stack使用pop()之后,栈顶元素并不是真正被删除了,只是我们访问不到了
}
我使用的方法是减少元素个数,因为我的top()访问就是通过元素个数。
总结
stack的实现还是很简单的,希望大家在看完这篇文章之后能够有所收获。若是这篇文章中有什么不足或是错误,希望大佬们在评论区指出或者私信联系我,我将立即修改,感激不尽!!!