【C++笔记】容器适配器及deque和仿函数

🔥个人主页:大白的编程日记
🔥专栏:C++笔记
 
文章目录
- 【C++笔记】容器适配器及deque和仿函数
 - 前言
 - 一.容器适配器
 - 1.1什么是容器适配器
 - 1.2 STL标准库中stack和queue的底层结构
 
- 二.stack
 - 2.1stack类模版
 - 2.2头文件问题
 
- 三.queue
 - 3.1queue类模版
 - 3.2按需实例化
 
- 四priority_queue
 - 4.1priority_queue的定义
 - 4.2仿函数
 
- 五.deque
 - 5.1deque的定义
 - 5.2deque的结构
 
- 后言
 
前言
哈喽,各位小伙伴大家好!上期我们讲了list结构剖析及其模拟实现。今天我们来讲一下dequeue和模版进阶。话不多说,我们进入正题!向大厂冲锋
一.容器适配器
1.1什么是容器适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

 容器适配器就像我们的电流适配器一样。
 
 容器适配器就是一种转化接口,把一种接口转化为我们需要的接口。
1.2 STL标准库中stack和queue的底层结构
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:
如果我们需要写一个栈的类模版。我们会自己手搓一个出来。
template<class T>
class stack
{
private:
	T* ptr;
	size_t size;
	size_t capacity;
};
 
但是栈只需要栈顶插入和删除,也就是说只需要支持一端插入和删除即可。那我们的vector和list容器都支持一端插入和删除。所以我们可以用vector和list容器封装实现。
所以我们的栈就可以用一个容器封装实现
    //container适配转化出Stack
	template<class T, class container = vector<T>>
	class Stack
	{
	public:
	private:
		container con;
	};
 
这里我们container传vector就是用vector适配转化的栈,传list就是list适配转化的栈。这就是我们通过container模版参数用容器适配器模式写的一个栈。
库里的栈也是这样做的。
 
 

二.stack
2.1stack类模版
这里我们用容器适配器写了一个栈的类模版。
 成员是container。成员函数再调用容器的接口即可实现一个先进先出的栈。
 默认使用vector容器适配。
//container适配转化出Stack
template<class T, class container = vector<T>>
class Stack
{
public:
	void push(T x)
	{
		con.push_back(x);
	}
	void pop()
	{
		con.pop_back();
	}
	const T& Top() const
	{
		return con.back();
	}
	size_t size() const
	{
		return con.size();
	}
	bool empty() const
	{
		return con.empty();
	}
private:
	container con;
};
 
2.2头文件问题
我们自己写的文件包含在.cpp文件时尽量放在最后面。防止出现以下错误。
 
三.queue
3.1queue类模版
这里queue我们也是使用适配器模式。因为queue要求一端进一端出。
 所以使用list容器适配更合适。也是调用对应容器的接口即可。
	//container适配转化出Queue
	template<class T, class container =list<T>>
	class Queue
	{
	public:
		void push(T x)
		{
			con.push_back(x);
		}
		void pop()
		{
			con.pop_front();
		}
		const T& front() const
		{
			return con.front();
		}
		const T& back() const
		{
			return con.back();
		}
		size_t size() const
		{
			return con.size();
		}
		bool empty() const
		{
			return con.empty();
		}
	private:
		container con;
	};
 
3.2按需实例化
类模版实例化是按需实例化。什么是按需实例化呢?
 
 所以这里我们没报错,因为我们没有调用pop_front.
 
 但当我们调用pop时就会报错。这就是因为按需实例化。
 
四priority_queue
4.1priority_queue的定义
priority_queue也是一个容器适配器.priority_queue也叫优先级队列
 
通过对priority_queue的底层结构就是堆,因此此处只需对对进行通用的封装即可。
 堆我们用下标计算父子关系。所以priority_queue的默认容器是vector.
这里我们直接用容器封装即可。
template<class T, class container = vector<T>,class Compare =Less<T>>
class priority_queue
{
public:
	void AdjustDown( int parent)
	{
		Compare com;
		int child = parent * 2 + 1;//孩子节点
		while (child < con.size())
		{
			int tmp = child;//左右孩子中最小的孩子
			if (tmp + 1 < con.size() && com(con[tmp], con[tmp + 1]))//防止没有右孩子
			{
				tmp = child + 1;
			}//假设法
			if (com(con[parent], con[tmp]))//判断
			{
				swap(con[parent], con[tmp]);//交换
				parent = tmp;
				child = parent * 2 + 1;
			}
			else
			{
				break;//调整完毕
			}
		}
	}
	void AdjustUp(int size)
	{
		Compare com;
		int child = size - 1;//最后的节点
		while (child > 0)
		{
			int parent = (child - 1) / 2;//父亲节点
			if (com(con[parent],con[child]))//判断
			{
				swap(con[child], con[parent]);//交换
				child = parent;
			}
			else
			{
				break;//调整完成
			}
		}
	}
	void push(const T& x)
	{
		con.push_back(x);
		AdjustUp(con.size());
	}
	void pop()
	{
		swap(con[0], con[con.size() - 1]);
		con.pop_back();
		AdjustDown(0);
	}
	const T& top()
	{
		return con[0];
	}
	size_t size() const
	{
		return con.size();
	}
	bool empty() const
	{
		return con.empty();
	}
private:
	container con;
};
 
4.2仿函数
那我们怎么调整大堆小堆呢?
 那就需要用到仿函数。
 仿函数是个类。
template<class T>
struct Less
{
	int operator()(const T& x, const T& y)
	{
		return x < y;
	}
};
 
仿函数这个类主要重载operator()。
 这个()里面传两个对象的比较。
Compare com;
if (com(con[parent],con[child]))//判断
 
这里看起来很像函数调用,但是实际上是operator()的调用。
 所以被叫做仿函数。仿函数我们想支持各种类型的比较就可以写成类模版。
template<class T, class container = vector<T>,class Compare =Less<T>>
 
所以我们在priority_queue的模版参数里传一个仿函数类型就可以控制大堆小堆。
 
 通过传不同仿函数类型,我们就能控制不同的比较逻辑
 从而控制大小堆。
 
五.deque
5.1deque的定义
我们可以发现库的的stack和queue没有用list和vector。
 而是用了一个dequeue的容器。
 
 那deque是什么呢?
 
deque是vector和list的缝合怪。
 
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
 
5.2deque的结构
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
 
 deque是通过一个指针数组来控制数据存储的数组。
 
-  
下标

 -  
迭代器
deque 迭代器结构有四个指针

 

deque借助两个迭代器维护,start就是第一个数据的迭代器,finish就是最后一个数据的迭代器。
 
-  
迭代器遍历

可以看到库里的实现和我们的差不多

 -  
头插尾插

 -  
中间插入删除
deque中间插入删除也需要挪动数据。 -  
operator[]
因为头插时第一个数组可能不满所以先用cur-first+n计算位置。
如果小于当前数组直接+n接口。
否则就用N去计算位置。 
 - 总结

后言
这就是容器适配器及deque和仿函数。大家自己好好消化。今天就分享到这!感谢各位的耐心垂阅!咱们下期见!拜拜~















![[进阶]集合的进阶(1)泛型](https://i-blog.csdnimg.cn/direct/54c863ef61274d2784473c39d6e58104.png)





