yo!这里是STL::适配器相关模拟实现

目录

前言

适配器介绍

deque介绍(了解)

 容器适配器与普通容器的联系

stack模拟实现

queue模拟实现

priority_queue模拟实现

介绍

实现

反向迭代器模拟实现

介绍

实现 

在list类中调用

在vector类中调用

后记


前言

        在介绍完string、vector、list类之后,对应着数据结构,应该介绍栈和队列了吧,yes!但是string、vector、list类是容器,而这里的栈和队列是属于容器适配器,既然谈到了适配器,这篇文章就把适配器的相关内容介绍一下吧,包括基本介绍、deque、stack、queue、priority_queue以及反向迭代器,难以理解的内容不多,主要通过这些内容将适配器是什么、适配器有什么用理解明白即可,而这些内容本身并没有什么复杂的东西,快往下看吧!

适配器介绍

        适配器(Adapter)是一种软件设计模式,它允许将接口不兼容的类或对象组合在一起工作。适配器模式相当于两个不相容的接口之间的中间层,它转换一个接口,以便让另一个接口能够与之兼容,从而使得两个不兼容的接口可以协同工作,即将一个类的接口转换为客户希望的另外一个接口。   ——摘抄引用

        适配器分为多种,今天我们讨论的是容器适配器,其他种后面遇到再总结,容器适配器是一种特殊类型的容器,它们提供了一种不同于标准容器的接口,但基于现有的 STL容器实现,以支持特定类型的操作。

        容器适配器可以被认为是容器的封装,它们使用已有容器的接口来实现常用的数据结构。比如栈、队列、优先队列,它们都是基于vector、deque或list等现有容器实现的。使用容器适配器可以更轻松地实现常见的数据结构,并且可以避免手动实现底层数据结构的复杂性和错误。

deque介绍(了解)

        deque(double-ended queue)是双端队列,可以在两端进行插入和删除操作,是高效的O(1)时间复杂度的操作,也可以在中间插入删除,但时间复杂度是O(N),deque可以看作是一个“数组”,但它并不是真正连续的空间,而是一段段连续的空间拼接,拼接的方法就是使用了链表的思想,连续的空间是使用了顺序表的思想,所以说,deque是与顺序表和链表对齐,而不是与栈和队列对齐。

        deque的常用操作包括push_front、push_back、pop_front、pop_back、insert、erase等(如图一),逻辑结构如图二所示。

        比如说,一小块连续空间的大小能存放3个(实际情况可以改,这里为了简化理解),先尾插1,2,3,4,再头插5,6,如下图。

 容器适配器与普通容器的联系

        说了半天适配器,又介绍了deque容器,那容器适配器与普通容器到底有啥区别,或者有啥联系,我们先看看官方文档中的栈与队列类的模板列表,如下图。

         可以发现,栈和队列的类模板列表中出现了Container的类型名,也就是在定义栈和队列时不仅需要传入数据类型,还要传入所使用的底层容器,而且可以发现,栈和队列的默认底层容器是deque,当然也可以使用vector或者list容器,这就是容器适配器与普通容器的联系。

        那为什么源码会使用deque作为栈和队列的默认底层容器呢?先来看一下deque对比vector和list的优势和劣势吧。①deque更适合头尾的插入删除,复杂度都是O(1),因为vector头尾插入时需要移动大量元素,list插入过多时需要扩容,不断向OS申请空间;②deque在中间插入删除效率并不高,而且并不适合遍历,这两点加起来就能充分说明deque很适合作为栈和队列的默认底层容器,而且可以看出栈和队列正是deque优点的结晶,这是deque应用之一,实则能应用到deque的地方并不多。

stack模拟实现

        通过数据结构课程的学习我们知道,栈可以使用数组也可以使用链表形式实现,所以这里底层容器Container可以使用vector或者list,但是官方使用了上面介绍的deque容器实现。

        stack类成员就是使用传过来的容器类定义的一个对象,无需构造函数和析构函数,因为成员变量只有一个自定义类型,构造和析构都是去调用它自己的函数,然后就是将stack所需的接口使用指定类实现即可,包括压栈、出栈、访问栈顶、以及对应const对象所对应的函数。

代码:

template <class T, class Container = deque<T>>
class Stack
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_back();
	}
	T& top()
	{
		return _con.back();
	}
	const T& top() const
	{
		return _con.back();
	}
	bool empty() const
	{
		return _con.empty();
	}
	size_t size() const
	{
		return _con.size();
	}
private:
	Container _con;
};

queue模拟实现

        队列的实现与栈一致,默认底层容器也是deque,唯一不同点就是队列的接口与栈不同,注意即可。

代码:

template <class T, class Container = deque<T>>
class Queue
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_front();
	}
	T& front()
	{
		return _con.front();
	}
	const T& front() const
	{
		return _con.front();
	}
	T& back()
	{
		return _con.back();
	}
	const T& back() const
	{
		return _con.back();
	}
	bool empty() const
	{
		return _con.empty();
	}
	size_t size() const
	{
		return _con.size();
	}
private:
	Container _con;
};

priority_queue模拟实现

  • 介绍

        priority_queue叫做优先队列,是队列的一种,也就说大部分实现也是与普通队列一致,核心不同点在于优先队列中的元素被赋予优先级。当出队时,优先级最高的元素先出队。在一个优先队列中,元素的顺序不仅仅取决于它们进入队列的顺序,还取决于它们的优先级。这里的优先级可以自己通过仿函数Com设置,比如整数中越大的优先级越高,或者字符串相比越大优先级越高,当然,底层容器Container也是可以自己设置,可以设置为vector、list、deque。

  • 实现

        官方设置vector作为priority_queue的底层容器,在vector的基础上又设置了堆算法将vector的元素构造成堆的结构,也可以说priority_queue就是个堆,且默认是大堆

        priority_queue的成员变量也是底层容器定义的一个对象,而且上面提到priority_queue就是个堆,所以需要构造函数将传入的元素先构造成一个堆,这里使用传迭代器构造的方式,实现过程就是在数据结构课程中学习的建队的过程,即从最后一个非叶节点开始从后向前对每个节点调用向下调整算法。值得注意的是,同时要写一个默认构造函数(无参或者全缺省的普通构造函数),否则就会报“没有默认构造函数”的错。

       入队操作(push)就是先尾插元素,再对此元素调用向上调整算法形成一个堆,出队操作(pop)就是将优先级最高的元素(即堆顶)与最后一个元素交换,将优先级最高元素pop出去,再对新堆顶元素调用向下调整算法形成一个堆,其他操作包括但不限于访问堆顶元素、判空等。

代码:

template <class T, class Container = vector<T>, class Com = less<T> >
class Priority_queue
{
public:
	Priority_queue()  
	{

	}

	template <class InputIterator>
	Priority_queue(InputIterator first, InputIterator last)
	{
		//插入数据
		while (first != last)
		{
			_con.push_back(*first);
			first++;
		}
		//建堆
		for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
		{
			adjust_down(i);
		}
	}

	void adjust_up(size_t child)
	{
		Com com;
		size_t p = (child - 1) / 2;
		while (child != 0)
		{
			//if (_con[child] > _con[p])
			//if (_con[p] < _con[child])
			if (com(_con[p], _con[child]))
			{
				std::swap(_con[child], _con[p]);
			}
			else
				break;
			child = p;
			p = (child - 1) / 2;
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		adjust_up(_con.size() - 1);
	}

	void adjust_down(size_t parent)
	{
		Com com;
		size_t ch = parent * 2 + 1;
		while (ch < _con.size())
		{
			//if (ch < _con.size() - 1 && _con[ch + 1] > _con[ch])
			//if (ch < _con.size() - 1 && _con[ch] < _con[ch + 1])
			if (ch<_con.size() - 1 && com(_con[ch], _con[ch + 1]))
			{
				ch++;
			}
			//if (_con[ch] > _con[parent])
			//if (_con[parent] < _con[ch])
			if (com(_con[parent] , _con[ch]))
			{
				std::swap(_con[parent], _con[ch]);
			}
			else
				break;
			parent = ch;
			ch = parent * 2 + 1;
		}
	}
	void pop()
	{
		std::swap(_con.front(), _con.back());
		_con.pop_back();
		adjust_down(0);
	}

	bool empty() const
	{
		return _con.empty();
	}

	T& top()
	{
		return _con.front();
	}
	const T& top() const
	{
		return _con.front();
	}
private:
	Container _con;
};

反向迭代器模拟实现

  • 介绍

        在介绍string、vector、list的迭代器过程中,无论是原生指针还是包装成一个类,我们都只了解到了普通迭代器和const迭代器,当初在了解stl的六大件时得知,迭代器不止有那些,还有反向迭代器以及反向const迭代器,之前是因为还未了解到适配器,所以将反向迭代器的介绍留在了这里。那为什么将反向迭代器留在适配器的地方讲呢?是因为反向迭代器是一个迭代器适配器,传进一个容器的迭代器,就能适配出对应的反向迭代器。

        与正向迭代器类似,反向迭代器可以倒序遍历容器中的元素,通过调用rbegin()和rend()方法获取反向迭代器的起始和结束位置,同时也可以使用*、->、++等操作符进行访问和移动。

        注意:也不是所有容器的迭代器都可以适配出反向迭代器,比如<forward_list>(单链表)、<unordered_map>、<unordered_set>,这些容器都不能逆向遍历。

  • 实现 

         根据介绍,反向迭代器也是使用类模板实现,模板参数中传入对应容器迭代器,再传入操作符*、->所需的数据类型的引用和指针形式。

        提到说反向迭代器是适配器,去调用对应容器适配器的接口以实现自己的接口,所以成员变量就是容器迭代器的类所定义的一个对象,构造函数则是根据传入的容器迭代器对象初始化反向迭代器对象。运算符++、--也是很简单,反向迭代器的++对应正向的--,反向的--对应正向的++。

        stl规定:rbegin对应end,rend对应begin,如下图

         这样的规定就导致了 解引用操作就是解引用当前迭代器的前一个迭代器,也是得到前一个迭代器的值,即先让迭代器--,再取值(如代码所示),同时->操作是取前一个迭代器的值的地址。而对于关系操作符则是比较成员对象(容器迭代器)是否相等,不做过多赘述。

代码:

template <class Iterator, class Ref, class Ptr>
class __reverse_iterator
{
public:
    //此typedef仅是简化反向迭代器的名字,无其他作用
	typedef __reverse_iterator<Iterator, Ref, Ptr> RIterator;  

	Iterator _cur;

	__reverse_iterator(Iterator x)
		:_cur(x)
	{

	}

	RIterator& operator++()
	{
		--_cur;
		return *this;
	}

	RIterator& operator--()
	{
		++_cur;
		return *this;
	}

	Ref operator*()
	{
		Iterator tmp = _cur;
		return *--tmp;
	}

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

	bool operator!=(const RIterator& x)
	{
		return _cur != x._cur;
	}
};
  • 在list类中调用

        在有了list迭代器和const迭代器的基础上,传入反向迭代器类模板,形成了list的反向迭代器的类__reverse_iterator<iterator, T&, T*>,根据stl规定,rbegin对应end,rend对应begin,对应情况如下图。

         注意:对于rbegin()的返回值reverse_iterator(end()),是通过传入end()迭代器构造一个反向迭代器传回,不过不显式构造也行,直接return end()就存在隐式类型转换,也是会调用构造函数构造,其他成员函数也如此。

代码:

	//这里的iterator就是list迭代器,const_iterator就是list的const迭代器
    //typedef作用是简化类名
    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());
	}
  • 在vector类中调用

        调用情况与list中一致,多写一份调用就多份理解,仔细琢磨一下。

代码:

	//这里的iteratior是vector类的迭代器
    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());
	}

后记

         目前阶段,适配器相关内容大概就包括以上内容,完整看下来的话可以发现,适配器就是借用已有容器的接口实现自己专用的接口,比如,你有苹果13(有苹果13的充电器),有一天你又买了一部苹果14,但没有苹果14的充电线,就用13的充电器,的确也可以冲,但是冲得慢且有时接触不良,效果不好,所以你就想,有苹果13的充电器,能不能买个转接头,插在13的充电器的头上就可以给14充电了,而且充电迅速且效果好。此时,转换头就是所谓的适配器。

        这么说,应该就很好理解了,上面常见的适配器相关介绍还有不懂的可以私我也可以评论区,加油,拜拜!


猜你喜欢

转载自blog.csdn.net/phangx/article/details/132178601
今日推荐