什么是适配器?
举个例子:在日常生活中,当手机没电了,我们需要给手机充电,给手机充电的方式很多,可以插到电源上,也可以用充电宝,还可以直接连着电脑充。而我们并不关心用什么给它充电,我们关心的只是能否给手机充上电。适配器充当的角色就是 给手机充电的接口,它会将不同大小的电压转化成适合给手机充电的电压。容器适配器的概念可以结合这个例子理解,它以 某种容器作为底层结构,改变其接口,使它符合该容器的特性。
stack ,queue,priority_queue都能存放元素,但都没有划分到容器中,因为容器底层都有自己的实现方法,而这三个底层都是将其他容器封装。借用侯捷老师书中的话就是,具有”修改某物的接口,形成另一种风貌“之性质者,被称为适配器(adapter),因此他们就被划分到 容器适配器(container adapter)
stack 是一种先进后出的数据结构 ,只允许在一端插入、删除、访问顶端元素。因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器。vector 和 list 都提供了这两种方法。所以这两个都可以用来作为stack的底层架构。但STL中默认选择的deque作为底层容器。这是为什么呢?
1、首先要了解deque是什么,它的结构是什么样?
我们知道vector的优点就是尾插和尾删很方便,支持随机访问,而且效率高,但是在任意位置插入和删除效率很低。而 list 的优势就是任意位置插入删除很方便,但是随机访问效率很低。两者就像是两个极端,都在自己的优势方面发挥到极致。这时候就有人想把两者的优点都结合起来,创造一个随机访问效率高,任意位置插入删除也很方便的容器。于是就有了deque。 deque 是双向开口的连续空间(逻辑上),可以在头和尾进行插入和删除(O(1)时间复杂度),vector也可以,但是效率很差。 先来了解deque的框架。 参考侯捷老师的《STL源码剖析》中的图 ,它是由 中控 + 缓冲区 构成的,中控就是图中的map,注意这里的map不是STL容器,它只是一段小的连续空间,里面存的每个元素都是指针,每个指针都指向一段较大的连续空间,就是图中的缓冲区。
2、它既然结合了vector和list的优点,那它就是完美的吗?
不是,为了实现在头和尾插入和删除都和方便,它必然要付出很大的代价,代价就是它的迭代器非常复杂。参考侯捷老师书中的图,可以看到deque维护了两个迭代器,start指向第一个缓冲区的第一个元素,finish指向最后一个缓冲区的最后一个元素的下一个位置。cur指向此迭代器所指缓冲区的当前元素,first指向此迭代器所指缓冲区的第一个元素,last指向迭代器所指缓冲区的最后一个元素,node指向中控。
因为要维持“双向连续的假象”,所以我们头插时,先找第一块缓冲区,看缓冲区满了吗?没有满,就说明还有空间,start 的cur++(注意++的方向是从last -> first这个方向),如果满了,就需要再申请一段缓冲区,然后在map中控上注册,就是让start的node指向map中的下一个节点,注意方向是图中的向左延申。因为是新开的空间需要将start重新指定,node就指向刚才在map中向左注册的一个节点,first指向缓冲区的起始位置,last指向缓冲区的末尾,cur指向last位置,将要插入的元素放入last位置,cur++;同样的,尾插就是看最后一个缓冲区是否满了,没有满,插入元素,cur++;满了,申请空间,在map上注册,放入元素,迭代器重置。这样头插头删尾删尾插的时间复杂度都是O(1),这是vector 和 deque的一个很大区别
3、vector 和 deque 的不同:
- deque支持在常数时间内进行头插和头删,而vector没有提供在头部进行操作的方法就是因为效率很低。(在技术上可以实现在头部操作 )
- deque没有容量的概念,它是把分段的连续空间动态组合起来,需要空间就申请再连起来,不需要像vector那样reserve()空间,也不需要考虑增容三部曲(开空间,拷数据,再释放旧空间)。
了解了deque,stack的模拟实现也就很简单了
template<class T, class Container = std::deque<T>>
class Stack{
public:
Stack() {};
void Push(const T& x)
{
_con.push_back(x);
}
void Pop()
{
_con.pop_back();
}
bool Empty()
{
return _con.empty();
}
T& Top()
{
return _con.back();
}
const T& Top() const
{
return _con.back();
}
size_t Size()
{
return _con.size();
}
private:
Container _con;
};
void TestStack()
{
Stack<int> s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
s1.Push(5);
s1.Push(6);
std::cout << "size:";
std::cout<< s1.Size()<<std::endl;
s1.Pop();
std::cout << "size:";
std::cout << s1.Size() << std::endl;
while (!(s1.Empty())) {
std::cout << s1.Top() << " ";
s1.Pop();
}
std::cout << std::endl;
}
queue (队列)具有先进先出的性质,只允许再一端插入,在另一端提取元素,如果要用标准模板类容器,底层容器要提供push_back(),pop_front()等方法, deque和lisi都满足要求。
template<class T,class Container =std::deque<T>>
class Queue {
public:
Queue(){}
void Push(const T& x)
{
_con.push_back(x);
}
void Pop()
{
_con.pop_front();
}
size_t Size()
{
return _con.size();
}
bool Empty()
{
return _con.empty();
}
T& Front()
{
return _con.front();
}
const T& Front()const
{
return _con.front();
}
T& Back()
{
return _con.back();
}
const T& Back()const
{
return _con.back();
}
private:
Container _con;
};
void TestQueue()
{
Queue<int> q;
q.Push(1);
q.Push(2);
q.Push(3);
q.Push(4);
q.Push(5);
q.Push(6);
std::cout << q.Size() << std::endl;
q.Pop();
std::cout << q.Size() << std::endl;
while (!(q.Empty())) {
std::cout << q.Front() << " ";
q.Pop();
}
std::cout << std::endl;
}
总结一下就是stack 可以使用:vector 、list 、deque 作为底层容器
queue可以使用 :list 、deque 作为底层容器