C++STL之stack和queue


前言

本篇文章内容主要和适配器有关,主要会介绍stack、queue、deque、反向迭代器。


一、stack和queue

简单使用:

这两个的使用没啥可讲的,一个后进先出,一个先进先出,简单提一下使用,重点在模拟

stack

queue

这两个接口都类似,只不过队列可以拿到队首和队尾,stack是栈顶。

empty判空,size就是大小,push入,pop出

模拟

库里的:

stack和queue都是容器适配器,对已经存在的东西进行复用,可以想一想用什么来复用?库里复用的是deque,这个我们后面讲,用C语言模拟的时候就可以用顺序表或者链表,那这里就可以用vector / list,对于stack,内部容器需要支持push_back,pop_back,这个用vector效率不错,对于deque需要支持尾插和头删,vector没有头删,可以用erase(begin())但是这样效率太低,可以用list,deque的问题后面讲!!!!

模拟:

cpp 复制代码
namespace myspace {
	template<class T, class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		size_t size()
		{
			return _con.size();
		}
		T& top()
		{
			return _con.back();
		}
		void pop()
		{
			_con.pop_back();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
	//先进先出
	template<class T,class Container = list<T> >
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		size_t size()
		{
			return _con.size();
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		void pop()
		{
			_con.pop_front();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

这里我们并不知道Container是啥,模板实例化是list就调用list的函数,是vector就调用vector的函数。

二、deque

deque到底是啥能让适配器使用的是这个呢?deque这么有用为啥不出名呢?

deque是双端队列,尾插尾删头插头插均为O(1),且支持[]访问下标,内部是随机迭代器,这么看有点像vector和list的结合体,与vector比较,头插效率高,不需要挪动数据;与list比较,空间利用率比较高,可以[]访问下标,那他有缺点吗?当然有,如果没有缺点我们还需要vector和list干嘛。

deque的底层比较复杂,所以我们只简单介绍

deque的底层是一小段一小段连续的空间拼接而成的,有一个中控器,通常是指针数组,每一个指针指向一个缓冲区,结构大致是这样

迭代器的实现更加复杂,这里不进行细致的讲解,因为不会。。
想了解可以看这个博客,非本人所写

deque确实有优点,但是缺点也很明显,遍历问题,遍历效率太低。

下面一段代码来检测三种容器的遍历速度:同时遍历N个元素

cpp 复制代码
int main() {
	const int N = 1e6;
	std::vector<int> vec(N);
	std::deque<int> deq(N);
	std::list<int> lst;
	srand(time(0));
	for (int i = 0; i < N; ++i) {
		int x = rand();
		vec[i] = deq[i] = x;
		lst.push_back(x);
	}

	auto t1 = clock();
	for (int x : vec) {}
	auto t2 = clock();
	std::cout << "Vector: " << (t2 - t1)  << "s/\n";

	t1 = clock();
	for (int x : deq) {}
	t2 = clock();
	std::cout << "Deque: " << (t2 - t1) << "s/\n";
	t1 = clock();
	for (int x : lst) {}
	t2 = clock();
	std::cout << "List: " << (t2 - t1)<< "s/\n";

	return 0;
}

1e6数据量下:

1e7:

这个结果很正常,因为vector内存连续,CPU高速缓存,list最慢,内部空间不连续,缓存速度较慢。deque的速度不快,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,但是容器要经常遍历,所以deque平常使用较少,也就容器适配器会用到

deque中间插入是O(n)又不如list,所以缺点也很明显

接着测试中间插入的效率:搞了一千个元素。第一次在中间插入,之后在新插入元素的下一个位置插入,测试效率。

cpp 复制代码
const int N = 1e7;
template<class T>
void Test_Time(T& _con)
{
	auto it = _con.begin();
	for (int i = 0; i < 500 && it != _con.end(); ++i) {
		++it;
	}
	int t1 = clock();
	for (int i = 0; i < N; ++i)
	{
		it = _con.insert(it, i);
		++it;
	}
	int t2 = clock();
	cout << t2 - t1 << endl;
}
int main() {
	std::vector<int> v(1000);
	std::deque<int> d(1000);
	std::list<int> l(1000);
	Test_Time(v);
	Test_Time(d);
	Test_Time(l);
	return 0;
}

1e6的数据下:(1e7跑不出来了,vector快还是和内存连续有很大关系)

测试尾插:

cpp 复制代码
const int N = 1e7;
template<class T>
void Test_Time(T& _con)
{
	int t1 = clock();
	for (int i = 0; i < N; ++i)
	{
		_con.push_back(i);
	}
	int t2 = clock();
	cout << t2 - t1 << endl;
}
int main() {
	std::vector<int> v;
	std::deque<int> d;
	std::list<int> l;
	Test_Time(v);
	Test_Time(d);
	Test_Time(l);
	return 0;
}

通过这些结果我们可以得出容器适配器使用deque的原因了:

1.stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

  1. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque的效率和内存使用率很高。

3.完美避开了它遍历容器效率低下和他的中间插入效率低下的特点,又完美利用了它两端插入和删除效率高效,内存利用率较list高的优点。

所以上面stack和queue的模拟都应该使用deque作为默认容器


三、反向迭代器

前面讲的vector和list和string都有反向迭代器,为什么现在才讲呢?因为反向迭代器的设计也利用了适配器的思想,它利用正向迭代器来实现反向迭代器并且一套模板所有带反向迭代器的都可以使用,反向迭代器++就是正向迭代器减减,反过来也是这样。

拿vector举例:

自己理解的确实没啥问题,rbegin在最后一个元素的位置,但是库里为什么这么设计呢?主要体现了一种对称,正向和反向正好对称,并且rbegin就是

reverse_iterator(end()),但是这样就需要控制*了,解引用实际上是解引用它的上一个元素,控制这点就行。

基本架子

里面封装一个正向迭代器,Ref和Ptr都和正向迭代器的思想类似

cpp 复制代码
template<class Iterator,class Ref,class Ptr>
class Reverse_Iterator
{
	typedef Reverse_Iterator<Iterator, Ref, Ptr> Self;
	Iterator _it;
public:
	Reverse_Iterator(Iterator it)
		:_it(it)
	{}
	Reverse_Iterator()
	{}
};

类里面分装反向迭代器:

cpp 复制代码
typedef Reverse_Iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_Iterator<const_iterator, const T&, const T*> const_reverse_iterator;

reverse_iterator rbegin()
{
	return reverse_iterator(end());
}
reverse_iterator rend()
{
	return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
	return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
	return const_reverse_iterator(begin());
}

完整

cpp 复制代码
template<class Iterator,class Ref,class Ptr>
class Reverse_Iterator
{
	typedef Reverse_Iterator<Iterator, Ref, Ptr> Self;
	Iterator _it;
public:
	Reverse_Iterator(Iterator it)
		:_it(it)
	{}
	Reverse_Iterator()
	{}
	Ref operator*()
	{
		Iterator tmp(_it);
		return *(--tmp);
	}
	Ptr operator->()
	{
		return &(operator*());
	}
	Self& operator++()
	{
		--_it;
		return *this;
	}
	Self operator++(int)
	{
		Iterator tmp(_it);
		--tmp;
		return tmp;
	}
	Self& operator--()
	{
		++_it;
		return *this;
	}
	Self operator--(int)
	{
		Iterator tmp(_it);
	    ++tmp;
		return tmp;
	}
	bool operator!=(const Self& x)const
	{
		return _it != x._it;
	}

	bool operator==(const Self& x)const
	{
		return _it == x._it;
	}
};

这套反向迭代器非常好用,套上就可以跑。
stack_queue完整代码
反向迭代器完整代码

四、总结

本次内容主要介绍了反向迭代器和适配器,适配器相对简单,deque虽然比较复杂但是不需要过多了解,知道优缺点即可,反向迭代器是一种很牛的设计,需要记住。

相关推荐
自学也学好编程几秒前
C++现代编程之旅:从基础语法到高性能应用开发
c++
☆璇37 分钟前
【C++】C++入门
开发语言·c++
真的想上岸啊2 小时前
学习C++、QT---27(QT中实现记事本项目实现行列显示、优化保存文件的功能的讲解)
c++·qt·学习
今晚打老虎2 小时前
让黑窗口变彩色:C++控制台颜色修改指南
开发语言·c++
CrimsonEmber2 小时前
ubuntu源码安装ceres-solves
c++
Murphy_lx3 小时前
C++中的list(1)
开发语言·c++·list
诺亚凹凸曼3 小时前
C++中byte*和char*的区别
c++
小蛋编程4 小时前
【图论】图的定义与一些常用术语
c++·算法
Peter(阿斯拉)4 小时前
[C/C++安全编程]_[中级]_[如何避免出现野指针]
c++·初始化列表·全局变量·野指针·类成员变量
Elnaij5 小时前
从C++开始的编程生活(3)——引用类型、内联inline和nullptr
开发语言·c++