【C++】STL容器-stack和queue的使用与模拟实现
目录
- 前言
- 一、stack介绍
- 二、stack的模拟实现
- push
- pop
- top
- empty
- size
- 三、queue介绍
- 四、queue的模拟实现
- push
- pop
- front/back
- size/empty
- 五、deque介绍
- 六、deque与vector和list的对比
前言
上文讲解了【C++】STL容器–list的模拟实现详情请点击,今天继续介绍另外两个STL容器–stack和queue,以及简单讲解一下deque
一、stack介绍
- stack是一种容器适配器,专门用于先进后出,只能从容器的一端进行数据的插入和删除操作,stack作为容器适配器被实现
- 容器适配器即对特定类进行封装作为底层容器, 并且提供一组特定的成员函数来访问其元素,stack的底层容器可以是任何标准容器类模板或其它特定的容器类,这些容器应该支持以下接口: push_back(尾插)、pop_back(尾删)、top(获取顶部数据)、size(返回容器有效数据)、empty(判断是否为空)
- 标准容器
vector、list、deque均符合这些要求,默认情况下,如果我们没有为stack指定特定的底层容器,默认采用deque作为其底层容器,针对deque,小编后面会进行介绍
二、stack的模拟实现

- 我们实现stack的时候要使用类模板来进行实现,T为stack存储的数据类型,模板参数列表中的模板参数还可以有缺省值,这个缺省值与函数的缺省值(函数的缺省值的是数据)不同的是模板参数的缺省值是类型,函数的缺省值是对象,默认情况下我们采用deque作为stack的底层容器
- 在使用deque这个容器的时候要包头文件#include < deque >
namespace gy
{template<class T, class Container = deque<T>>class stack{public:private:Container _con;};
}
push
push:将数据进栈,直接调用尾插即可
void push(const T& x)
{_con.push_back(x);
}
pop
pop:将数据出栈,直接调用尾删即可
void pop(){_con.pop_back();}
top
top:返回容器顶部数据,直接调用back即可
T& top()
{return _con.back();
}
empty
- 判断是否为空,直接调用empty函数即可,由于只需要判断是否为空,不需要修改数据,因此使用const修饰,让普通对象和const对象都可以进行调用
bool empty()const
{return _con.empty();
}
size
- 使用size函数即可返回stack中有效数据个数,由于我们不需要对内容进行修改,因此使用const修饰,让普通对象和const对象都可以进行调用
size_t size()const
{return _con.size();
}
void test_stack1()
{gy::stack<int> st;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);cout << st.size() << endl;cout << st.empty() << endl;while (!st.empty()){cout << st.top() << " ";st.pop();}cout << endl;
}
结果如下:
- stack是先进后出,所以插入的顺序是1、2、3、4、5,后面出栈的顺序是5、4、3、2、1
三、queue介绍
1.和stack一样是一种容器适配器,专门用于先进先出,从容器的一端插入数据,从容器的另一端提取数据
2. queue的容器应该支持以下接口:push_back(尾插)、pop_front(头删)、front(获取头部数据)back(获取尾部数据)、size、empty
3. 标准容器list、deque均符合这些要求,默认情况下,如果我们没有为queue指定特定的底层容器,默认采用deque作为其底层容器,针对deque,后文将会进行简单介绍
四、queue的模拟实现

namespace gy
{template<class T, class Container = deque<T>>class queue{public:private:Container _con;};
}
push
- push:根据队列的性质,只能尾插数据,因此直接调用尾插函数即可
void push(const T& x)
{_con.push_back(x);
}
pop
- pop:根据队列的性质,只能头删数据,因此直接调用头删函数即可
void pop()
{_con.pop_front();
}
front/back
- 返回头部数据和尾部数据,调用底层适配器的front和back函数即可
T& front()
{return _con.front();
}T& back()
{return _con.back();
}
size/empty
size_t size()const
{return _con.size();
}bool empty()const
{return _con.empty();
}
void test_queue()
{gy::queue<int> q;q.push(1);q.push(2);q.push(3);q.push(4);cout << q.size() << endl;cout << q.empty() << endl;cout << q.back() << endl;while (!q.empty()){cout << q.front() << endl;q.pop();}cout << endl;
}
结果如下:
五、deque介绍
- deque:双端队列,是一种双开口的“连续”空间的数据结构,双开口的含义是可以在头尾两端进行数据的插入和删除操作,且时间复杂度为O(1)
- 同时支持了operator[]即随机访问(vector的性质),和头插头删(list的性质)尾插尾删,但是它实际上并没有撼动vector和list的地位,并不能达到替代vector和list的目的
六、deque与vector和list的对比
vector
- 优点:
- 支持下标随机访问快,尾插尾删效率高
- CPU命中率高
- 缺点:
- 头部或中间位置插入删除效率底
- 插入空间不够需要扩容,扩容有一定的性能消耗,倍数级扩容可能存在一定的空间浪费
list
- 优点:
- 在任意位置O(1)插入删除
- 按需申请释放空间
- 缺点:
- 不支持下标随机访问
- CPU高速缓存命中率低,还存在缓存污染
deque
- deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个
动态的二维数组,
deque与vector对比
优势:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的
deque与list对比
优势:其底层是连续空间,空间利用率比较高,不需要存储额外字段
deque的缺点
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
- operator[]的计算过程:
- 先看是否在第一个用于头插的空间(buff)中,如果在那么使用位置进行访问
- 如果不在第一个buff中,将下标 i 减去第一个空间(buff)的元素个数大小size,得出新的下标i
- 使用下标 i / 空间中的数据个数——>得出是在第几个空间(buff)
- 使用下标 i % 空降中的数据个数——>得出是在该空间中的第几个位置








