STL六大组件——容器适配器

什么是适配器?

举个例子:在日常生活中,当手机没电了,我们需要给手机充电,给手机充电的方式很多,可以插到电源上,也可以用充电宝,还可以直接连着电脑充。而我们并不关心用什么给它充电,我们关心的只是能否给手机充上电。适配器充当的角色就是 给手机充电的接口,它会将不同大小的电压转化成适合给手机充电的电压。容器适配器的概念可以结合这个例子理解,它以    某种容器作为底层结构,改变其接口,使它符合该容器的特性。

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 的不同: 

  1. deque支持在常数时间内进行头插和头删,而vector没有提供在头部进行操作的方法就是因为效率很低。(在技术上可以实现在头部操作 )
  2. 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 作为底层容器

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值