stl适配器+deque容器

114 阅读10分钟

适配器

适配器是一种设计模式,【设计模式类似于孙子兵法,36计,适配器就类似于其中一个计谋】

适配器将一个类的接口转换成另一种接口.

容器适配器

容器适配器是把特定的类作为底层容器,并提供一组特定成员函数来访问其元素.

以栈为例子,对于栈的实现:

(1) 底层可以使用vector容器来实现,尾部当栈顶

(2) 底层也可以使用list容器来实现

stl栈和队列

stl的栈和队列不是容器,而是容器适配器.

cplusplus.com文档上的截图:

image.png image.png

image.png image.png

T:存储的数据类型
Container:底层可以指定使用容器,默认用deque容器

--栈模拟实现

namespace lyh
{
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:
		void push(const T& val)//入栈
		{
			_con.push_back(val);
		}

		void pop()//出栈
		{
			_con.pop_back();
		}

		T& top()//栈顶
		{
			return _con.back();
		}

		bool empty()//判空
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

void myStackTest()
{
	//显式传vector作为栈的底层容器
	lyh::stack<int, vector<int>> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
}

容器要能提供以下接口,才能作为stack的底层容器.

cplusplus.com

image.png

--队列模拟实现

namespace lyh
{
	template<class T, class Container >
	class queue
	{
	public:
		//入队列
		void push(const T& val)
		{
			_con.push_back(val);
		}

		//出队列
		void pop()
		{
			_con.pop_front();
		}
		
		//队头
		T& front()
		{
			return _con.front();
		}

		//队尾
		T& back()
		{
			return _con.back();
		}

		//判空
		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

void myQueueTest()
{
	lyh::queue<int, list<int>> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
}

cplusplus.com文档 image.png

deque容器

deque和vector、list一样,都是stl里的容器,

一种数据结构,也是栈和队列的默认使用容器.

--简化模型【只能了解到它的功能】

image.png

cplusplus.com

image.png

--具体模型【deque的物理存储方式】

image.png

deque是一种分段连续的数据结构,连续是假象,分段是事实,

A 不进行任何操作:

( 1 ) 它会有多段连续的buffer空间,在这些buffer里存放数据.

( 2 ) 但如何把这么多段buffer连接起来?

用一个指针数组,按顺序存放这些buffer的地址(一般叫中控数组)

( 3 ) 刚开始时,buffer地址数组是从中间开始使用,保证两边都可以填充数据.

B 进行尾插头插/尾删头删时:

( 1 ) deque里会保存第一个元素的位置start,和最后一个元素的下一个位置finish.

因此可以随时对头尾进行插入删除

( 2 ) 模拟实现时要考虑【buffer地址数组的扩容】,【buffer空间的申请释放】.

--迭代器模拟实现

迭代器成员分析

sgi版stl中的deque迭代器有4个成员属性

image.png

cur : 指向存储的数据

first : 数据所在的缓冲区buffer的起始位置

last : 数据所在缓冲区buffer的末尾

node : T**类型,

( 1 ) 哪里保存当前buffer地址?

中控数组/buffer地址数组的某个位置

( 2 ) 在用迭代器进行遍历deque时,迭代器必须有能力去跳到下一个buffer中,

所以迭代器必须保存 管理当前buffer 的位置

image.png

模拟实现代码

#include <string.h>
#include <assert.h>
namespace lyh
{
	int bufferSize = 10;
	template<class T, class Ref, class Ptr>
	struct dequeIterator
	{
	public:    //成员变量
		//具体数据的指针
		T* _cur;

		//标识 具体数据所在buffer 的开始和结束
		T* _first;
		T* _last;

		//缓冲区地址数组,第n个位置保存了当前buffer首地址,标识该第n个位置的地址
		//也称为中控器
		T** _buffer_adr_array_n;
	public:
		typedef dequeIterator<T, Ref, Ptr> Iterator;

		//用deque里具体数据的地址,和维护当前数据缓冲区的中控器,来构造iterator
		dequeIterator(T* val,T** buffer_adr_array_n)
		{
			_cur = val;
			_buffer_adr_array_n = buffer_adr_array_n;
			if (_cur != nullptr)
			{
				_first = *buffer_adr_array_n;
				_last = *buffer_adr_array_n + bufferSize;
			}
			else
			{
				_first = nullptr;
				_last = nullptr;
			}
		}

		//Iterator it;
		//++it
		//即让迭代器指向下一个元素
		//思路:++_cur,如果_cur != _last,说明没有超过当前buffer
		//如果_cur == _last,说明要跳转到下一个buffer首地址
		Iterator& operator++()
		{
			++_cur;
			if (_cur == _last)
			{
				T* nextBuffer = *(++_buffer_adr_array_n);
				_cur = nextBuffer;
				//跳转以后,要重新标识边界
				_first = nextBuffer;
				_last = nextBuffer + bufferSize;
			}
			return *this;
		}

		//--it
		//即让迭代器指向前一个元素
		//思路:先判断_cur == _first
		//如果 _cur !=_first, 只需要--_cur
		//_cur = _first, 跳转到上一个buffer的尾元素
		Iterator operator--()
		{
			if (_cur != _first)
			{
				--_cur;
				return *this;
			}
			else
			{
				T* preBuffer = *(--_buffer_adr_array_n);
				_cur = preBuffer + bufferSize - 1;
				_first = preBuffer;
				_last = preBuffer + bufferSize;
				return *this;
			}
		}

		//it += 5
		Iterator& operator+=(int step)
		{
			//即让迭代器从_cur开始 向前走step步,求得新指向位置
			//思路:_cur + step 看成 _first + (_cur - _first) + step
			// 让迭代器从_first开始 向前走【 (_cur - _first) + step】 步

			size_t offset = _cur - _first + step;
			if (offset < bufferSize)
			{
				//没有超过当前所在buffer
				_cur += step;
			}
			else
			{
				//从_first开始,需要的步数,跨越了几个buffer
				size_t bufferNum = offset / bufferSize;
				//多出的步数,就是最终所在buffer的位置
				size_t pos = offset % bufferSize;

				_buffer_adr_array_n += bufferNum;
				_cur = *_buffer_adr_array_n + pos;
				_first = *_buffer_adr_array_n;
				_last = *_buffer_adr_array_n + bufferSize;
			}
			return *this;
		}

		Iterator operator+(int step)
		{
			Iterator tmp = *this;
			tmp += step;
			return tmp;
		}

		// it2 - it1 默认it2指向后面的元素,返回两个迭代器相差的距离 
		size_t operator-(const Iterator& it1)
		{
			//如果在同一个buffer中
			if (it1._buffer_adr_array_n == _buffer_adr_array_n)
			{
				return _cur - it1._cur;
			}
			else
			{
				//it1所在buffer的剩下元素 + it2所在buffer的剩下元素 + 之间完整buffer的所有元素
				int num2 = _cur - _first;
				int num1 = it1._finish - it1._cur;
				int tmp = (_buffer_adr_array_n - (it1._buffer_adr_array_n + 1)) * bufferSize;
				return num1 + num2 + tmp;
			}
		}

		bool operator==(const Iterator& it1)
		{
			return _cur == it1._cur;
		}

		bool operator!=(const Iterator& it1)
		{
			return _cur != it1._cur;
		}

		bool operator<(const Iterator& it1)
		{
			return _buffer_adr_array_n < it1._buffer_adr_array_n;
		}
		bool operator>=(const Iterator& it1)
		{
			return !(*this < it1);
		}
		bool operator<=(const Iterator& it1)
		{
			return (*this == it1) || (*this < it1);
		}
		bool operator>(const Iterator& it1)
		{
			return !(*this <= it1);
		}

		Ref operator*()
		{
			return *_cur;
		}

		Ptr operator->()
		{
			return _cur;
		}

		Ref operator[](size_t n)
		{
			return *(*this + n);
		}

	};

--优缺点

优点

( 1 ) 头尾插入删除效率高,适合做stack和queue的默认底层容器

( 2 ) 支持[]随机访问

缺陷

( 1 ) [ ]的计算比vector的复杂,速度不如vector

( 2 ) 中间的插入删除仍然需要挪动数据,这方面不如链表快.

优先级队列priority_queue

仿函数

仿函数,本质是一个类,类里重载了()运算符,即

返回值 operator()(函数形参列表){}

例:下面是仿函数greater的定义

namespace yh
{
    template<class T>
    struct greater
    {
            bool operator()(const T& val1, const T& val2)const
            {
                    return val1 > val2;
            }
    };
}
void testFunctor()
{
	//用仿函数定义一个对象
	yh::greater<int> functor;

	bool ret = functor(1, 2);//本质是functor.operator()(1, 2)
	std::cout << ret;
}

仿函数的对象,可以像函数一样去使用.

在优先级队列【本质是堆】中,把仿函数作为模板参数,

用户通过传仿函数less/greater,可以控制要大堆还是小堆.

一般传less,建大堆;传greater,建小堆.

优先级队列简介

它也是stl中的容器适配器,【本质是堆】.

cplusplus.com: image.png

实现代码

堆的数据结构讲解:

数据结构——二叉树(2) - 掘金 (juejin.cn)

在向上/向下调整算法进行比较时,使用Compare仿函数的对象,

(1) 默认把该对象当成less的,将大的数据往上换,小的数据往下调整,

(2) 默认建成大堆即可.

	template<class T, class Container = std::vector<T>, class Compare = std::less<T>>
	class priority_queue
	{
	private:
		Container _con;
	public:
		//对自定义类型,初始化列表会去调用它的默认构造
		priority_queue(){}
		
		//用一段迭代器区间构造
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			//相当于建堆,建堆最好用向下调整算法为O(N)

			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			//从倒数第一个非叶子结点开始,使用向下调整算法建堆
			for (int i = (size() - 1 - 1) / 2; i >= 0; --i)
			{
				//size() - 1是最后一个结点位置,(size()-1 - 1) / 2是最后一个结点的父结点
				adjustDown(i);
			}
		}
		
		
		//向优先级队列插入数据,插入后保证它仍然是一个堆
		//使用向上调整算法
		void push(const T& val)
		{
			_con.push_back(val);
			adjustUp(_con.size() - 1);
		}
		//将pos位置元素,向上调整(比较它与它的祖先结点)
		void adjustUp(size_t pos)
		{
			static Compare compare;

			size_t parent = (pos - 1) / 2;
			//若pos = 0,没有父亲结点,不能再向上调整
			while (pos > 0)
			{
				//小于建大堆,默认建大堆
				if (compare(_con[parent], _con[pos]))//默认是_con[parent] < _con[pos]
				{
					//pos向上调整
					std::swap(_con[parent], _con[pos]);

					//更新pos和它的parent
					pos = parent;
					parent = (pos - 1) / 2;
				}
				else
					break;
			}
		}
		
		//删除堆顶数据,一般用来选出最大最小的元素
		//1 交换 堆顶元素和尾元素
		//2 尾删
		//3 对此时堆顶数据 用向下调整算法
		void pop()
		{
			assert(size() > 0);
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjustDown(0);
		}
		void adjustDown(size_t pos)
		{
			static Compare compare;
			//前提是左边和右边都是大堆或小堆

			//当没有左孩子时,调整结束
			size_t mChild = 2 * pos + 1;
			while (mChild < size())
			{
				if (mChild + 1 < _con.size() && compare(_con[mChild], _con[mChild + 1]) )//默认是_con[mChild] < _Con[mChild+1]
					++mChild;

				if (compare(_con[pos], _con[mChild]))
				{
					std::swap(_con[pos], _con[mChild]);

					pos = mChild;
					mChild = pos * 2 + 1;
				}
				else
					break;
			}
		}
		
		size_t size() const
		{
			return _con.size();
		}
		bool empty() const 
		{ 
			return _con.empty(); 
		}
		const T& top() const 
		{ 
			return _con.front(); 
		}
	};
	void testPriorityQueue()
	{
		int arr[] = { 9, 7, 15, 1, 86, 47, 55 };
		//默认使用vector作为底层容器,默认传less建大堆,大的元素优先级高
		priority_queue<int> pq(arr, arr + sizeof(arr) / sizeof(int));
		//用一段迭代器区间构造pq

		//不断取堆顶的数,然后pop堆顶数据,从大到小输出
		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
		}
	}

image.png

反向迭代器(迭代器适配器)

void testUse6()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
}

image.png

以list的迭代器为例子:

begin()是第一个有效结点的迭代器,

end()是最后一个有效结点的下一个迭代器位置,即指向哨兵位头结点

image.png

--反向迭代器与正向迭代器的不同

( 1 ) 【反向迭代器的起点、终点】可能和正向迭代器的不同

image.png

( 2 )正向迭代器的--,就是反向迭代器的++;

反向迭代器的++,就是正向迭代器的--

--用正向迭代器构造出反向迭代器

反向迭代器与正向迭代器在实现上,只有++和--不同,

因此,反向迭代器内部可以封装正向迭代器.

反向迭代器也称为 【 迭代器适配器 】.

//提供list的迭代器,就能适配出list的反向迭代器
//提供vector的迭代器,就能适配出vector的反向迭代器
namespace yh
{
	template<class Iterator, class Ref, class Ptr>
	class Reverse_Iterator
	{
		//成员属性
		Iterator _it;

		typedef Reverse_Iterator<Iterator,Ref, Ptr> RIterator;

		Reverse_Iterator(Iterator it)
			:_it(it)
		{}

		RIterator& operator++()
		{
			//反向迭代器++,就--封装的_it
			--_it;
			return *this;
		}

		RIterator& operator--()
		{
			//反向迭代器--,就++封装的_it
			++_it;
			return *this;
		}

		//其它运算符实现,直接复用正向迭代器即可
		Ref operator*()
		{
			return *_it;
		}

		Ptr operator->()
		{
			return _it.operator->();
		}

		bool operator!=(const RIterator& rit)
		{
			return _it != rit._it;
		}
		bool operator==(const RIterator& rit)
		{
			return _it == rit._it;
		}
	};
}

--stl中的反向迭代器设计

结论

在list内部,

反向迭代器的起点 就是 正向迭代器的终点

反向迭代器的终点 就是 正向迭代器的起点

image.png

实现方式

stl反向迭代器所在的文件是

image.png

current就是封装的正向迭代器.

image.png

仍然以链表为例子:

image.png

观察得到:反向迭代器真正指向的数据,在前一个位置,所以只需要修改*的返回值即可.

		Ref operator*()
		{
			//实现 反向迭代器的rbegin() 就是 正向迭代器的end()
			// 反向迭代器的rend() 就是 正向迭代器的begin()
			// 反向迭代器所指的位置 其实是 前一个元素
			return *(_it - 1);
		}

完整模拟实现反向迭代器代码

namespace yh
{
	template<class Iterator, class Ref, class Ptr>
	class Reverse_Iterator
	{
	private:
		//成员属性
		Iterator _it;
		typedef Reverse_Iterator<Iterator,Ref, Ptr> RIterator;
	public:
		Reverse_Iterator(Iterator it)
			:_it(it)
		{}

		RIterator& operator++()
		{
			//反向迭代器++,就--封装的_it
			--_it;
			return *this;
		}

		RIterator& operator--()
		{
			//反向迭代器--,就++封装的_it
			++_it;
			return *this;
		}

		Ref operator*()const
		{
			//实现 反向迭代器的rbegin() 就是 正向迭代器的end()
			// 反向迭代器的rend() 就是 正向迭代器的begin()
			// 反向迭代器所指的位置 其实是 前一个元素
			Iterator tmp = _it;
			--tmp;
			return *tmp;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		bool operator!=(const RIterator& rit)
		{
			return _it != rit._it;
		}
		bool operator==(const RIterator& rit)
		{
			return _it == rit._it;
		}

	};
}

在list内部的代码:

		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());
		}
		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}
		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(begin());
		}