1、什么是容器适配器
理解容器适配器之前,我们需要了解什么是适配器,其实和我们现实中的适配器是差不多的理解。也可以去了解下设计模式中的适配器模式。
容器适配器是一个封装了序列容器的类模板。是在一般的序列容器的基础上提供了一些功能。所谓的容器适配器,目的就是为了适配基础容器现有的接口来提供不同的功能。
适配器类在基础序列容器的基础上实现了一些自己的操作。它们提供的优势是简化了公共接口,而且提高了代码的可读性。
STL标准库中目前有3种容器适配器。stack、queue、priority_queue。
下表显示了stl标准库中的3种容器适配器的比较。
容器适配器 | 基础序列容器满足条件 | 基础序列容器 | 默认序列容器 |
---|---|---|---|
stack | 先进后出(FILO) push_back() pop_back() | vector、list、deque | deque |
queue | 先进先出(FIFO) front() back() push_back() pop_front() | list | deque |
priority_queue | 有权重,默认第一个元素权重最高 | vector、deque | vector |
我们今天主要来看stack适配器。
2、stack 容器的定义
stack是一种先进后出(FILO)的数据结构,只有一个出口,通常被称作是栈顶。因为只有一个出口,因此对于数据的存取,访问元素只能访问离开口最近的元素,只有当这个元素被移除之后,才能访问下一个元素。
我们先大概的看一下stack的图形示意。
看下stl中stack的源码:
template <class _Tp, class _Sequence>
class stack {
// requirements:
__STL_CLASS_REQUIRES(_Tp, _Assignable);
__STL_CLASS_REQUIRES(_Sequence, _BackInsertionSequence);
typedef typename _Sequence::value_type _Sequence_value_type;
__STL_CLASS_REQUIRES_SAME_TYPE(_Tp, _Sequence_value_type);
public:
typedef typename _Sequence::value_type value_type;
typedef typename _Sequence::size_type size_type;
typedef _Sequence container_type;
typedef typename _Sequence::reference reference;
typedef typename _Sequence::const_reference const_reference;
protected:
_Sequence c;
public:
stack() : c() {}
explicit stack(const _Sequence& __s) : c(__s) {}
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference top() { return c.back(); }
const_reference top() const { return c.back(); }
void push(const value_type& __x) { c.push_back(__x); }
void pop() { c.pop_back(); }
};
通过上面的源码我们能够看到,基本上stack的成员函数操作都只是调用了其内置序列容器的方法。
3、stack 容器创建
#include <stack>
using namespace std;
1、 创建一个不包含任何元素的 stack 适配器,并采用默认的 deque 基础容器
std::stack<int> data;
2、创建指定底层容器的 stack 适配器
上面我们介绍容器适配器的时候,看到,满足 stack 容器适配器功能的底层序列容器有3个,vector、list、deque。stack 默认是以deque为底层容器的。下面我们看看另外两种的创建。
std::stack<int, std::vector<int>> data; //以vector为底层容器,创建一个空的stack
//以有元素的vector为基础序列容器创建的stack含有vector的元素
std::vector<int> vec{1, 2, 3};
std::stack<int, std::vector<int>> data(vec);
stack<int, std::list<int>> data; //以list为底层容器,创建一个空的stack
std::list<int> values {5, 6, 7, 8};
std::stack<int, std::list<int>> data(values);
3、赋值
std::list<int> values {5, 6, 7, 8};
std::stack<int, std::list<int>> data(values);
std::stack<int, std::list<int>> data1 = data;
通过这种赋值的方法来创建新的stack适配器,是不会影响第一个适配器的,也就是是不会影响data的。
4、迭代器
stack没有其他的访问元素的方法,也就意味着,stack是不能被遍历的,因此也不提供迭代器。
5、成员函数
成员函数 | 函数说明 |
---|---|
empty() | 判断stack是否为空,为空返回true,否则返回false |
size() | 返回stack中的元素的实际个数 |
(const) top() | 返回栈顶元素的引用(const),栈为空则报错 |
push(const T& val) | 调用底层的push_back函数,先复制,压入副本 |
push(T&& obj) | 移动元素的方式压入栈,底层容器支持 |
pop() | 弹出栈顶元素 |
emplace(arg…) | arg… 可以是一个参数,也可以是多个参数,但它们都只用于构造一个对象,并在栈顶直接生成该对象,作为新的栈顶元素 |
swap(stack & other_stack) | 将两个 stack 适配器中的元素进行互换,两个栈的基础容器、元素类型必须相同 |
6、使用
我们通过一个例子来看下stack的创建以及使用。
#include <iostream>
#include <stack>
#include <list>
#include <vector>
#include <cstring>
using namespace std;
template<class T>
void display(T& data) //注意函数的入参,会修改传入的变量
{
// int nCount = data.size();
// for(int nIndex = 0; nIndex < nCount; ++nIndex) //如果使用for循环遍历,则不能用data.size()直接代替nCount,因为data.pop()会改变data.size()
while (!data.empty())
{
cout << data.top() << " ";
data.pop();
}
cout << endl;
}
int main(int argc, char* argv[])
{
stack<int> data0;
for(int nIndex = 0; nIndex < 10; ++nIndex)
{
data0.push(nIndex);
}
display(data0); // 9 8 7 6 5 4 3 2 1 0
stack<int, vector<int>> data;
for(int nIndex = 0; nIndex < 10; ++nIndex)
{
data.push(nIndex);
}
display(data); // 9 8 7 6 5 4 3 2 1 0
vector<int> primes {1, 2, 3, 4};
stack<int, vector<int>> data1(primes);
display(data1); // 4 3 2 1
stack<int, list<int>> data2;
for(int nIndex = 1; nIndex <= 10; ++nIndex)
{
data2.push(nIndex * 2);
}
display(data2); // 20 18 16 14 12 10 8 6 4 2
list<int> values {5, 6, 7, 8};
stack<int, list<int>> data3(values);
display(data3); // 8 7 6 5
list<int> value {11, 12, 13, 14};
stack<int, list<int>> data4(value);
stack<int, list<int>> data5 = data4;
display(data5); // 14 13 12 11
display(data4); // 14 13 12 11
return 0;
}
运行结果:
需要注意的是:display函数的入参的形式,会影响传入的stack。比如:如果传入的是引用,则进行一轮打印遍历之后,则传入的stack中的元素会全部被pop掉。此时的stack为空。