stack与queue:底层实现与容器适配器

本文代码已同步Github

一、stack的介绍与使用

1、介绍

首先还是先给出stack的接口文档:stack使用参考文档(stack - C++ Reference)

stack属于特殊的线性表,其特点是LIFO(后进先出)

对于stack,我们在前期的数据结构中已经学过其结构特点

同时通过数组来模拟实现其底层结构

传送门:数据结构入门:栈实现全解析

2、使用

下面我们来看一下其接口

相较于vector,liststack的接口少了很多;

我们同样整理出核心接口

函数说明 接口说明
stack() 构造空的栈
empty() 检测 stack 是否为空
size() 返回 stack 中元素的个数
top() 返回栈顶元素的引用
push() 将元素 val 压入 stack 中
pop() 将 stack 中尾部的元素弹出

都是一些很简单的接口,依次来使用

二、stack的模拟实现

注意⚠️:我们先根据 stack 的数据结构特点完成最直观的模拟实现

1、底层结构分析

由于stack后进先出特殊结构,我们该用什么样的方式来实现呢?

在前面学习数据结构时,我们采用顺序存储的结构来进行模拟实现;

为什么选择顺序存储呢?

答案就是栈本身就可以被看成一个特殊的一维数组:

push就是尾插,pop就是尾删,top就是末尾元素

那现在我们直接选择封装好的底层容器来进行模拟实现,省去了造轮子的功夫

对于push_backpop_backvectorlist都有这两个接口,该怎么选呢?

stack 只需要在一端进行插入和删除,而 vector 的尾插、尾删都非常适合这一场景,因此可以使用 vector 作为底层容器

2、代码实现

① 基础框架

首先来完成基础框架的搭建,底层采用封装好的vector

cpp 复制代码
//stack.h
#pragma once
#include <vector>

namespace stl
{
	template<class T>
	class stack
	{
	public:

	private:
		std::vector<T> _v;
	};
}

下面来思考一下是否需要构造函数?

当成员变量是自定义类型时,构造函数会调用自定义类型的构造函数;

对于内置类型则是未定义的行为;'

显然,当前类不需要手动实现构造函数

② 核心接口

实现核心接口其实就是调用底层的vector的接口即可

cpp 复制代码
public:
	void push(const T& x)
	{
		_v.push_back(x);
	}

	void pop()
	{
		_v.pop_back();
	}

	T& top()
	{
		return _v.back();
	}

	const T& top() const
	{
		return _v.back();
	}

	const size_t size() const
	{
		return _v.size();
	}

	bool empty()
	{
		return _v.empty();
	}

下面来测试一下

三、queue的介绍与使用

1、介绍

同样地,先给出queue的接口文档:queue的使用参考文档(queue - C++ Reference)

queue同样属于特殊的线性表,其特点是FIFO(先进先出)

对于queue,我们在学习数据结构时已经通过链式存储进行实现

同样给出传送门:数据结构队列详解:从概念到代码实现

2、使用

先来看接口

整理出核心接口

函数声明 接口说明
queue() 构造空的队列
empty() 检测队列是否为空,是返回 true,否则返回 false
size() 返回队列中有效元素的个数
front() 返回队头元素的引用
back() 返回队尾元素的引用
push() 在队尾将元素 val 入队列
pop() 将队头元素出队列

下面我们依次来测试使用

四、queue的模拟实现

注意⚠️:我们先根据 queue 的数据结构特点完成最直观的模拟实现

1、底层结构分析

由于queue时先进先出,需要多次头删,vector显然就不合适了;

那么便采用list作为底层容器来说实现queue

2、代码实现

① 基础框架

cpp 复制代码
//queue.h
#pragma once
#include <list>

namespace stl
{
	template<class T>
	class queue
	{
	public:

	private:
		std::list<T> _lt;
	};
}

由于成员变量时自定义类型,同样不需要手动实现构造函数;

编译器默认生成的构造函数对于自定义类型便会调用其构造函数;

② 核心接口

stack的模拟实现同理,调用list的接口即可

cpp 复制代码
public:
	void push(const T& x)
	{
		_lt.push_back(x);
	}

	void pop()
	{
		_lt.pop_front();
	}

	T& front()
	{
		return _lt.front();
	}

	const T& front() const
	{
		return _lt.front();
	}

	T& back()
	{
		return _lt.back();
	}

	const T& back() const
	{
		return _lt.back();
	}

	const size_t size() const
	{
		return _lt.size();
	}

	bool empty()
	{
		return _lt.empty();
	}

来测试一下

五、容器适配器

1、介绍适配器模式

容器适配器 是 C++ 标准库中的一种类模板 ,它本身不是完整的容器,而是对底层容器(如 vectordequelist)的一层封装,目的是提供更精简、更专用的操作接口。

我们来看文档中对stackqueue的介绍

发现对stackqueue的描述是容器适配器,并非容器;

因此,我们可以通俗的理解:给通用容器套上一个"固定外壳",只保留特定场景需要的动作

同时能够发现:

stackqueue的底层采用的是deque作为容器

为什么呢?我们下一篇便会详细介绍;

2、优化代码

为了和文档中使用的容器保持一致,我们需要优化stackqueue的模拟实现

先对刚才实现的stack进行优化

其实就是加上deque作为类的模板参数

cpp 复制代码
//stack.h
#pragma once
#include <deque>

namespace stl
{
	template<class T,class Container = std::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();
		}

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

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


	private:
		Container _con;
	};
}

接着来优化queue的实现

cpp 复制代码
#pragma once
#include <list>

namespace stl
{
	template<class T,class Container = std::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();
		}

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

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

当没有显示传入用什么容器作为参数时,默认就会用缺省值deque来作为底层容器;

如果觉得有帮助,可以关注Github项目持续更新

相关推荐
Hello_Damon_Nikola1 小时前
CH573从入门到精通
c语言·开发语言·单片机·嵌入式硬件
Logintern091 小时前
Langgraph使用MemorySaver建立有记忆的图
开发语言·windows·python
豆沙沙包?2 小时前
C++~~~vector容器(p31-P40)
开发语言·c++
小小de风呀2 小时前
de风——【从零开始学习C++】(十八):AVL树——让二叉搜索树自己“站起来“的自平衡黑科技
c++·科技·学习
原野池予2 小时前
深入Java集合框架:数组与List互转底层原理剖析(JDK 8)
java·数据结构·list
必须会一定会2 小时前
GLM-5.3 迁移实战:强制思考、1M 上下文与官方编程基准
android·开发语言·kotlin
牛油果子哥q2 小时前
C++STL容器详解:vector/list/string底层扩容、迭代器失效终极解决、容器选型避坑
开发语言·c++·list
meilindehuzi_a2 小时前
Next.js 全栈基础实战:从 SPA、SSR 到 App Router 与 Todo API
开发语言·javascript·ecmascript
Chen—LSN2 小时前
C语言——数据在内存中的存储
c语言·开发语言·经验分享·笔记