【STL】stack和queue的实现原理

stack

一、概述

stack是一个先进后出(FILO)的数据结构,它只有一个出口,如图所示:
【STL】stack和queue的实现原理_第1张图片
stack允许新增元素(push)、移除元素(pop)、取得最顶端的元素,但没有任何办法取得其他元素。stack不允许有遍历行为。

本章所采用list版本为 SGI STL 2.91版本。

二、stack完整定义

以某种既有容器作为底部结构,将其接口改变,使之符合“先进后出”的特性,形成一个 stack,是很容易做到的。deque 是双向开口的数据结构,若以 deque 为底部结构并封闭其头端开口,便轻而易举地形成了一个 stack。因此,STL便以 deque 作为缺省情况下的 stack 底部结构。

由于 stack 系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为 adapter(配接器)(其他博客后续会讲到),因此 stack 往往被归类为容器配接器。

#define __STL_NULL_TMPL_ARGS <> 
//模板传入的是一个类
template <class T, class Sequence = deque<T> >//deque默认选择deque,并且使用alloc内存分配,缓冲区大小为512字节
class stack {
	  friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);//声明友元
	  friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);//声明友元
public:
	  typedef typename Sequence::value_type value_type;//
	  typedef typename Sequence::size_type size_type;
	  typedef typename Sequence::reference reference;
	  typedef typename Sequence::const_reference const_reference;
protected:
	  Sequence c;//定义一个deque对象
public:
	  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(); }//出栈
};

template <class T, class Sequence>
bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {//定义友元
	  return x.c == y.c;
}

template <class T, class Sequence>
bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {//定义友元
	  return x.c < y.c;
}//至于为什么这里需要用友元,可以参考effective c++

三、stack的迭代器

因为stack不提供遍历功能,所以并不支持迭代器。

四、以其他容器作为stack的底层容器

我们通过观察stack容器代码得知,使用的底层容器函数有empty,size,back,push_back,pop_back。而这些函数,list和vector都具备,所以可以用list和vector来代替deque。

#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
	cout << "using vector:" << endl;
	stack<int, vector<int>> vector_stack;
	vector_stack.push(1);
	vector_stack.push(2);
	vector_stack.push(3);
	vector_stack.push(4);
	cout << vector_stack.size() << endl;
	cout << vector_stack.top() << endl;
	vector_stack.pop();
	cout << vector_stack.top() << endl;
	vector_stack.pop();
	cout << vector_stack.top() << endl;
	vector_stack.pop();
	cout << vector_stack.top() << endl;
	cout << vector_stack.size() << endl;
	cout << "using list:" << endl;
	stack<int, list<int>> list_stack;
	list_stack.push(1);
	list_stack.push(2);
	list_stack.push(3);
	list_stack.push(4);
	cout << list_stack.size() << endl;
	cout << list_stack.top() << endl;
	list_stack.pop();
	cout << list_stack.top() << endl;
	list_stack.pop();
	cout << list_stack.top() << endl;
	list_stack.pop();
	cout << list_stack.top() << endl;
	cout << list_stack.size() << endl;
}

【STL】stack和queue的实现原理_第2张图片

当我们换用其他容器时,例如set:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
	cout << "using set:" << endl;
	stack<int, set<int>> set_stack;
	//代码运行到这里没问题
	set_stack.push(1);
	cout << set_stack.top() << endl;
}

【STL】stack和queue的实现原理_第3张图片
如果只是单纯声明一个容器 set_stack,并不会报错。但是如果使用某些函数,编译器会进行报错。

queue

一、概述

queue是一种先进先出(FIFO)的结构,他有两个出口,允许从低端加入元素(push)、取得顶端元素、移除元素(pop)。除了这两种方法,没有其他办法取得queue其他元素,所以,queue也是不允许遍历行为。如下图所示:
【STL】stack和queue的实现原理_第4张图片

二、queue完整定义

对于queue结构来说,deque是很容易满足的。deque是双向开口的数据结构,所以加以封装,就可以轻而易举完成一个queue。

由于 queue 系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为 adapter(配接器)(其他博客后续会讲到),因此 stack 往往被归类为容器配接器。

template <class T, class Sequence = deque<T> >
class queue {
	  friend bool operator== __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
	  friend bool operator< __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
public:
	  typedef typename Sequence::value_type value_type;
	  typedef typename Sequence::size_type size_type;
	  typedef typename Sequence::reference reference;
	  typedef typename Sequence::const_reference const_reference;
protected:
  	  Sequence c;
public:
	  bool empty() const { return c.empty(); }
	  size_type size() const { return c.size(); }
	  reference front() { return c.front(); }
	  const_reference front() const { return c.front(); }
	  reference back() { return c.back(); }
	  const_reference back() const { return c.back(); }
	  void push(const value_type& x) { c.push_back(x); }
	  void pop() { c.pop_front(); }
};

template <class T, class Sequence>
bool operator==(const queue<T, Sequence>& x, const queue<T, Sequence>& y) {
  	return x.c == y.c;
}

template <class T, class Sequence>
bool operator<(const queue<T, Sequence>& x, const queue<T, Sequence>& y) {
  	return x.c < y.c;
}

三、queue的迭代器

因为queue不提供遍历功能,所以并不支持迭代器。

四、以其他容器作为queue的底层容器

我们通过观察queue容器代码得知,使用的底层容器函数有empty,size,back,push_back,pop_front。而这些函数,list都具备,所以可以用list来代替deque。

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
	cout << "using list:" << endl;
	queue<int, list<int>> list_queue;
	list_queue.push(1);
	list_queue.push(2);
	list_queue.push(3);
	list_queue.push(4);
	cout << list_queue.size() << endl;
	cout << list_queue.front() << endl;
	list_queue.pop();
	cout << list_queue.front() << endl;
	list_queue.pop();
	cout << list_queue.front() << endl;
	list_queue.pop();
	cout << list_queue.front() << endl;
	cout << list_queue.size() << endl;
}

【STL】stack和queue的实现原理_第5张图片
我们发现list是可以的,并且符合先进先出的规则。

当我们用vector时:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
	cout << "using vector:" << endl;
	queue<int, vector<int>> vector_queue;
	vector_queue.push(1);
	vector_queue.push(2);
	vector_queue.push(3);
	vector_queue.push(4);
	cout << vector_queue.size() << endl;
	cout << vector_queue.front() << endl;
	vector_queue.pop();
	cout << vector_queue.front() << endl;
	vector_queue.pop();
	cout << vector_queue.front() << endl;
	vector_queue.pop();
	cout << vector_queue.front() << endl;
	cout << vector_queue.size() << endl;
}

【STL】stack和queue的实现原理_第6张图片

由于,vector并没有头部删除,读取函数,所以并不能像stack一样使用vector作为底层容器。

当我们换用其他容器,例如set时:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
	cout << "using set:" << endl;
	queue<int, set<int>> set_queue;
	//代码运行到这里没问题
	set_queue.push(1);
	cout << set_queue.front() << endl;
}

在这里插入图片描述
如果只是单纯声明一个容器 set_queue,并不会报错。但是如果使用某些函数,编译器会进行报错。

总结

stack和queue都是容器适配器,都属于对底层容器进行包装,然后再进行封装所需函数等。这涉及到我们后面要讲到的六大组件之适配器。同时,这种手法其实也是23中设计模式中的一种,适配器模式。

对于这两者的使用,重点还是要理解他们的特性,运用每种数据结构不同的特性来实现相应的功能。

从上面使用set作为stack底层容器、使用vector和set作为queue作为底层容器,我们可以看出单纯声明容器,并不会报错,主要是源自于编译器对于模板并不是全面检查。有关模板相关,等小学生学会再分享给大家。

参考资料:
《STL源码剖析》 - 侯捷

你可能感兴趣的:(C/C++,c++,容器,stack,queue,stl)