📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新更多有用的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- [1. stack:栈容器适配器](#1. stack:栈容器适配器)
-
- [1.1 核心特性](#1.1 核心特性)
- [1.2 常用接口](#1.2 常用接口)
- [1.3 stack 接口总结](#1.3 stack 接口总结)
- [2. queue:队列容器适配器](#2. queue:队列容器适配器)
-
- [2.1 核心特性](#2.1 核心特性)
- [2.2 常用接口](#2.2 常用接口)
- [2.3 queue 接口总结](#2.3 queue 接口总结)
- [3. stack 与 queue 的模拟实现](#3. stack 与 queue 的模拟实现)
-
- [3.1 stack 模拟实现](#3.1 stack 模拟实现)
- [3.2 queue 模拟实现](#3.2 queue 模拟实现)
- [3.3 为什么 stack 和 queue 的底层容器默认是 deque?](#3.3 为什么 stack 和 queue 的底层容器默认是 deque?)
- [4. 按需实例化:模板的惰性编译](#4. 按需实例化:模板的惰性编译)
-
- [4.1 概念](#4.1 概念)
- [4.2 代码示例](#4.2 代码示例)
- [5. deque:双端队列详解](#5. deque:双端队列详解)
-
- [5.1 什么是 deque?](#5.1 什么是 deque?)
- [5.2 deque 常用接口](#5.2 deque 常用接口)
- [5.3 deque 接口总结](#5.3 deque 接口总结)
- [5.4 deque 的适用场景](#5.4 deque 的适用场景)
- [6. deque 的底层结构:中控器 + 缓冲区](#6. deque 的底层结构:中控器 + 缓冲区)
-
- [6.1 整体架构](#6.1 整体架构)
- [6.2 两个核心组件](#6.2 两个核心组件)
-
- [1. 缓冲区(buffer)](#1. 缓冲区(buffer))
- [2. 中控器(map)](#2. 中控器(map))
- [6.3 扩容效率对比](#6.3 扩容效率对比)
- [7. 完整测试用例](#7. 完整测试用例)
-
- [Test 1:stack 使用 vector 作为底层容器](#Test 1:stack 使用 vector 作为底层容器)
- [Test 2:queue 使用 list 作为底层容器](#Test 2:queue 使用 list 作为底层容器)
- 8.总结
- 本文全部代码
-
- [🐾 Queue.h](#🐾 Queue.h)
- [🐾 Stack.h](#🐾 Stack.h)
- [🐾 Test.cpp](#🐾 Test.cpp)
前言
stack(栈)和 queue(队列)是 C++ 标准库中的容器适配器------它们不直接管理内存,而是通过封装底层容器(如 deque、vector、list)来实现特定的访问规则:
| 容器 | 访问规则 | 比喻 |
|---|---|---|
stack(栈) |
后进先出(LIFO) | 叠盘子:后放的先取 |
queue(队列) |
先进先出(FIFO) | 排队买票:先来的先服务 |
- 而 deque(双端队列)是它们的默认底层容器,兼具数组的随机访问和链表的头尾高效操作。
🐶 🐾 ✨ 🐾 🐶
1. stack:栈容器适配器
1.1 核心特性
栈是一种后进先出(LIFO) 的数据结构,仅能访问栈顶元素,不支持随机访问
- 访问规则:
只能从栈顶添加或删除元素,就像叠盘子------后放的盘子先被取走。 - 头文件:
#include<stack>
1.2 常用接口
cpp
#include<stack>
using namespace std;
void test_stack()
{
stack<int> st;
st.push(1); // 入栈:向栈顶添加元素
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " "; // 获取栈顶元素
st.pop(); // 删除栈顶元素
}
cout << endl; // 输出:4 3 2 1
}
1.3 stack 接口总结
| 接口 | 作用 | 注意事项 |
|---|---|---|
push(val) |
向栈顶添加元素 | 新元素成为新的栈顶 |
pop() |
删除当前栈顶元素 | 无返回值,需先确保栈非空 |
top() |
返回栈顶元素的引用 | 可读可改,需先确保栈非空 |
size() |
返回栈中元素个数 | 返回 size_t 类型 |
empty() |
判断栈是否为空 | 常用于遍历或删除前判断 |
🐶 🐾 ✨ 🐾 🐶
2. queue:队列容器适配器
2.1 核心特性
队列是一种先进先出(FIFO) 的数据结构
- 访问规则:`从队尾添加元素,从队头删除元素,就像排队买票------先来的先服务。
- 头文件:
#include<queue>
2.2 常用接口
cpp
#include<queue>
using namespace std;
void test_queue()
{
queue<int> q;
q.push(1); // 入队列:向队尾添加元素
q.push(2);
q.push(3);
q.emplace(4); // C++11:原地构造元素
while (!q.empty())
{
cout << q.front() << " "; // 获取队头元素
q.pop(); // 删除队头元素
}
cout << endl; // 输出:1 2 3 4
}
2.3 queue 接口总结
| 接口 | 作用 | 注意事项 |
|---|---|---|
push(val) |
向队尾添加元素 | 新元素成为最后一个元素 |
pop() |
删除队头元素 | 需先通过 front() 获取元素 |
front() |
返回队头元素的引用 | 可读可改,需确保队列非空 |
back() |
返回队尾元素的引用 | 可读可改,需确保队列非空 |
size() |
返回队列中元素个数 | 返回 size_t 类型 |
empty() |
判断队列是否为空 | 常用于遍历前判断 |
🐶 🐾 ✨ 🐾 🐶
3. stack 与 queue 的模拟实现
3.1 stack 模拟实现
cpp
namespace MyStack
{
// 模板参数:T 为元素类型,Container 为底层容器类型
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& x) { _con.push_back(x); } // 入栈:尾插
void pop() { _con.pop_back(); } // 出栈:尾删
const T& top() const { return _con.back(); } // 栈顶:取尾部元素
bool empty() { return _con.size() == 0; }
size_t size() { return _con.size(); }
private:
Container _con; // 底层容器
//_con是Container的缩写
};
}
🐾 stack 的所有操作都转换为对底层容器的操作:
push -> push_back(尾插)pop -> pop_back(尾删)top -> back(取尾部)
3.2 queue 模拟实现
cpp
namespace MyQueue
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x) { _q.push_back(x); } // 入队:尾插
void pop() { _q.pop_front(); } // 出队:头删
const T& front() { return _q.front(); } // 队头:取头部元素
const T& back() { return _q.back(); } // 队尾:取尾部元素
bool empty() { return _q.size() == 0; }
size_t size() { return _q.size(); }
private:
Container _q;
};
}
🐾 queue 的操作:
push -> push_back(尾插)pop -> pop_front(头删)front -> front(取头部)back -> back(取尾部)
3.3 为什么 stack 和 queue 的底层容器默认是 deque?
| 容器 | push_back | pop_back | push_front | pop_front | operator |
|---|---|---|---|---|---|
vector |
O(1) 均摊 | O(1) | O(n) | O(n) | O(1) |
list |
O(1) | O(1) | O(1) | O(1) | O(n) |
deque |
O(1) | O(1) | O(1) | O(1) | O(1) |
结论 :deque 在头尾操作上都是 O(1),同时支持随机访问,是 stack 和 queue 最理想的底层容器。
🐶 🐾 ✨ 🐾 🐶
4. 按需实例化:模板的惰性编译
4.1 概念
C++ 模板的一个特性:
一个类被实例化后,不是所有成员函数都会被实例化,只有被调用到的函数才会被编译。
4.2 代码示例
cpp
amespace MyStack
{
// 模板参数:T为元素类型,Container为底层容器类型
//Container适配转换出stack
//_con是Container的缩写
template<class T, class Container = deque<T>>
class stack
{
public:
//入栈
void push(const T& x)
{
_con.push_back(x);
}
//出栈
void pop()
{
_con.pop_back();
}
//按需实例化
//void pop()
//{
// _con.pop_front();
// //首先我们要清楚栈是不能出"栈底"元素的,所以这个在功能上就不对
// //其次如果我们Container实例化成的是vector容器,而vector是没有pop_front()这个接口的
// //所以正常来说是有问题的,但是当我们没有调用该函数运行程序时是不会报错的
// //原因就是按需实例化:当一个类被实例化后不是类中所有的成员函数都会实例化,
// //而是根据你是否调用了该函数来进行实例化,而没有被实例化的成员函数其内部编译器是不会仔细查看的
// //所以即使Container实例化成的是vector容器,只要我们不调用这个函数也不会报错
// //但是只要我们调用了该函数编译器就会查找vector容器是否有该接口,若没有就会报错
//}
//获取栈顶元素
const T& top() const
{
return _con.back();
//不管是vector还是list等容器都有通用的获取尾部数据接口back()
//所以这里必须写成_con.back();这种写法
}
//判空
bool empty()
{
return _con.size() == 0;
}
//获取数据个数
size_t size()
{
return _con.size();
}
private:
Container _con;
//_con的类型为模板参数Container,当Container实例化成具体容器时
//模拟实现stack,_con调用的接口就会转换成对应容器的接口使用
};
🐶 🐾 ✨ 🐾 🐶
5. deque:双端队列详解
-
vector(是数组) :
物理空间连续,可支持快速下标随机访问,尾插、尾删效率极高(但是头删和中间位置随机删除/插入效率低下(时间复杂度为O(N)),且插入可能触发扩容)- 适用场景:适合基于随机访问的排序算法
-
list(链表) :
物理空间不连续,不支持快速下标随机访问,数据访问效率差(任意位置插入/删除效率极高(时间复杂度为O(1);插入无需扩容,可按需申请空间与释放内存) -
deque(顺序容器) :
双端队列,是STL中一种双开口的"连续"空间数据结构,将stack和list的功能进行取长补短(即用一个数组来存放指针,而指针的内容是一段连续的空间)
5.1 什么是 deque?
deque(double-ended queue,双端队列)是 STL 中的一种双开口的连续空间数据结构,融合了 vector 和 list 的优点:
两端高效操作:头尾插入/删除时间复杂度 O(1)随机访问:支持 运算符,时间复杂度 O(1)动态扩容:不需移动全部元素,效率高于 vector迭代器稳定性:两端插入/删除不使迭代器失效
5.2 deque 常用接口
cpp
void test_deque1()
{
deque<int> dq;
// 尾插
dq.push_back(3);
dq.push_back(4);
dq.push_back(5); // 3 4 5
// 头插
dq.push_front(2);
dq.push_front(1); // 1 2 3 4 5
// 随机访问
dq[4] *= 10; // 1 2 3 4 50
for (auto e : dq)
{
cout << e << " ";
}
cout << endl; // 1 2 3 4 50
}
5.3 deque 接口总结
| 接口 | 作用 |
|---|---|
push_front(val) |
在队头插入元素 |
push_back(val) |
在队尾插入元素 |
pop_front() |
删除队头元素 |
pop_back() |
删除队尾元素 |
front() |
获取队头元素的引用 |
back() |
获取队尾元素的引用 |
operator[](i) |
随机访问第 i 个元素 |
size() |
返回元素个数 |
empty() |
判断是否为空 |
clear() |
清空所有元素 |
5.4 deque 的适用场景
适合只需头尾操作、无需频繁遍历的场景
🐶 🐾 ✨ 🐾 🐶
6. deque 的底层结构:中控器 + 缓冲区
6.1 整体架构
deque 不是真正连续的内存,而是通过中控器管理多个缓冲区来实现逻辑上的连续:
6.2 两个核心组件
1. 缓冲区(buffer)
存储实际数据的最小单元固定大小的内存块(如默认 512 字节)每个缓冲区独立存在装满后申请新缓冲区,不迁移旧数据
2. 中控器(map)
本质是一个动态数组(指针数组)存储各个缓冲区的首地址当中控器装满时,申请更大的中控器只需拷贝指针,无需迁移缓冲区中的实际数据
6.3 扩容效率对比
| 容器 | 扩容方式 | 效率 |
|---|---|---|
vector |
申请新内存 -> 移动所有元素 -> 释放旧内存 | O(n) |
deque |
申请新缓冲区 -> 中控器新增指针 | O(1) 均摊 |
🐶 🐾 ✨ 🐾 🐶
7. 完整测试用例
Test 1:stack 使用 vector 作为底层容器
cpp
void test_stack1()
{
// 基于 vector 的栈(也可以使用 list、deque)
MyStack::stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " "; // 4 3 2 1
st.pop();
}
}
Test 2:queue 使用 list 作为底层容器
cpp
void test_queue1()
{
// 基于 list 的队列(vector 不适合,因为没有 pop_front)
MyQueue::queue<int, list<int>> q1;
q1.push(1);
q1.push(2);
q1.push(3);
q1.push(4);
while (!q1.empty())
{
cout << q1.front() << " "; // 1 2 3 4
q1.pop();
}
}
🐶 🐾 ✨ 🐾 🐶
8.总结
🐾 stack、queue 和 deque 的核心知识:
| 容器 | 核心特性 | 适用场景 |
|---|---|---|
stack |
LIFO,只能栈顶操作 | 函数调用栈、括号匹配、逆序输出 |
queue |
FIFO,队尾入队头出 | 任务队列、BFS 算法、消息队列 |
deque |
双端高效操作 + 随机访问 | 作为 stack/queue 的底层容器 |
🐾 三种容器该如何选择?
| 需求 | 适配容器 |
|---|---|
需要后进先出 |
使用 stack |
需要先进先出 |
使用 queue |
需要头尾操作 + 随机访问 |
使用 deque |
只需要头尾操作 |
使用 deque 或 list |
🐾 容器适配器的设计思想:
- stack 和 queue 不直接管理内存
- 通过封装底层容器实现特定访问规则
- 用户可以自定义底层容器(只要支持所需接口)
🐶 🐾 ✨ 🐾 🐶
本文全部代码
🐾 Queue.h
cpp
#pragma once
//Queue(队列):先进先出
//
//访问规则:从"队尾"添加元素,从"队头"删除元素(最先入队的元素最先入队)
//
//头文件:#include<queue>
//----------------------常用接口--------------------------------------------------------
//push(val):向队列的"队尾"添加一个元素,新元素成为队列的最后一个元素,操作后队列长度+1
//
//pop():删除队列的"队头"元素(即最早入队的元素),操作后队列长度-1,无返回值(需先通过front()获取队头元素再删除)
//
//front():返回队列"队头"元素的引用(可读可改),仅访问不删除,需确保队列非空
//
//back():返回队列"队尾"元素的引用(可读可改),仅访问不删除,需确保队列非空
//
//size():返回队列中当前元素总个数,返回值类型为size_t(无符号整数)
//
//empty():判断队列是否为空:若队列中无元素则返回true,有元素则返回false,常用于遍历或删除前判断队列状态
//基本用法代码演示
//#include<queue>
//using namespace std;
//void test_queue()
//{
// queue<int> a;
// a.push(1);
// a.push(2);
// a.push(3);
// a.emplace(4);
// while (!a.empty())
// {
// cout << a.front() << " ";
// a.pop();
// }
// cout << endl;
//}
//int mian()
//{
// test_queue();
// return 0;
//}
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace MyQueue
{
template<class T, class Container = deque<T>>
class queue
{
public:
//入队列
void push(const T& x)
{
_q.push_back(x);
}
//出队列
void pop()
{
_q.pop_front();
//通过这个接口我们就能知道vector是不适合作为队列的底层容器的
//首先在实现上vector本身就没有pop_front()这个接口,无法调用
//其次队列出数据是头部出,而vector头删数据的效率是非常低的(时间复杂度为O(N))
//所以队列的实现我们就不会用vector作为底层容器
}
//获取队头数据
const T& front()
{
return _q.front();
}
//获取队尾数据
const T& back()
{
return _q.back();
}
//判空
bool empty()
{
return _q.size() == 0;
}
//获取数据个数
size_t size()
{
return _q.size();
}
private:
Container _q;
};
}
🐾 Stack.h
cpp
#pragma once
//stack(栈):后进后出,仅能访问栈顶元素,不支持随机访问
//
//访问规则:只能从栈顶添加或删除元素(最后入栈的元素最先出栈),可理解成水杯
//
//头文件:#include<stack>
//-------------------常用接口使用--------------------------------------------
//push(val):向栈顶添加元素,新元素成为新的栈顶
//
//pop():删除当前栈顶元素(操作后原栈顶元素的下一个元素成为新的元素栈顶,无返回值,需先确保栈是非空的)
//
//top():返回栈顶元素的引用(可直接读取或修改栈顶值),也需先确保栈是非空的
//
//size();返回栈中当前储存的元素总个数,返回值为无符号整数(size_t)
//
//empty();判断是否为空,若栈中无元素则返回true,fouze返回false
////基本用法代码演示:
//#include<stack>
//using namespace std;
//void test_stack()
//{
// stack<int> st;
// st.push(1);
// st.push(2);
// st.push(3);
// st.push(4);
//
// while (!st.empty())
// {
// cout << st.top() << " ";
// st.pop();
// }
// cout << endl;
//}
//
//int main()
//{
// test_stack();
//}
//
//
//
//实例化:指的是C++模板(包括函数模板和类模板)的代码,只有真正被使用的时候,编译器才会为其生成具体的类型版本
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace MyStack
{
// 模板参数:T为元素类型,Container为底层容器类型
//Container适配转换出stack
//_con是Container的缩写
template<class T, class Container = deque<T>>
class stack
{
public:
//入栈
void push(const T& x)
{
_con.push_back(x);
}
//出栈
void pop()
{
_con.pop_back();
}
//按需实例化
//void pop()
//{
// _con.pop_front();
// //首先我们要清楚栈是不能出"栈底"元素的,所以这个在功能上就不对
// //其次如果我们Container实例化成的是vector容器,而vector是没有pop_front()这个接口的
// //所以正常来说是有问题的,但是当我们没有调用该函数运行程序时是不会报错的
// //原因就是按需实例化:当一个类被实例化后不是类中所有的成员函数都会实例化,
// //而是根据你是否调用了该函数来进行实例化,而没有被实例化的成员函数其内部编译器是不会仔细查看的
// //所以即使Container实例化成的是vector容器,只要我们不调用这个函数也不会报错
// //但是只要我们调用了该函数编译器就会查找vector容器是否有该接口,若没有就会报错
//}
//-------------------------"_con是Container的缩写"----------------------------------
//获取栈顶元素
const T& top() const
{
return _con.back();
//不管是vector还是list等容器都有通用的获取尾部数据接口back()
//所以这里必须写成_con.back();这种写法
}
//判空
bool empty()
{
return _con.size() == 0;
}
//获取数据个数
size_t size()
{
return _con.size();
}
private:
Container _con;
//_con的类型为模板参数Container,当Container实例化成具体容器时
//模拟实现stack,_con调用的接口就会转换成对应容器的接口使用
};
}
🐾 Test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
#include"Stack.h"
#include"Queue.h"
void test_stack1()
{
//基于底层容器为数组的栈
//此时模板参数T实例化成int,Container实例化成vector<int>
MyStack::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();
}
cout << endl;
}
//int main()
//{
// test_stack1();
// return 0;
//}
//////////////////////////////////////////////////////////////////////////////////////////////////////
void test_queue1()
{
//MyQueue::queue<int, vector<int>> q1;
MyQueue::queue<int, list<int>> q1;
q1.push(1);
q1.push(2);
q1.push(3);
q1.push(4);
while (!q1.empty())
{
cout << q1.front() << " ";
q1.pop();
}
cout << endl;
}
//int main()
//{
// test_queue1();
// return 0;
//}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//vector(是数组):物理空间连续,可支持快速下标随机访问,尾插、尾删效率极高
// 但是头删和中间位置随机删除/插入效率低下(时间复杂度为O(N)),且插入可能触发扩容
// 适用场景:适合基于随机访问的排序算法
//
//list(链表):物理空间不连续,不支持快速下标随机访问,数据访问效率差
// 任意位置插入/删除效率极高(时间复杂度为O(1);插入无需扩容,可按需申请空间与释放内存
//
//--deque(顺序容器):双端队列,是STL中一种双开口的"连续"空间数据结构,将stack和list的功能进行取长补短
// 即用一个数组来存放指针,而指针的内容是一段连续的空间
//1.两端高效操作:在队头(push_front/pop_front)和队尾(push_front/pop_back)插入/删除元素的时间复杂度都是O(1)
//
//2.随机访问:支持[] 运算符和 at() 方法,像数组一样直接访问任意位置的元素,时间复杂度为O(1)
//
//3.动态扩容:deque的内存结构是分段的(不像vector是连续的一块内存),扩容时不需要移动全部元素,效率更高
//
//4.迭代器稳定性:在两端插入/删除元素时不会使原有迭代器失效(中间插入/删除会失效)
//--deque的底层结构
//1.缓冲区(buffer):存储实际数据的最小单元,是固定大小的内存块(比如默认大小为512字节),每个缓冲区独立存在
// 当数据装满一个缓冲区后,会申请一个新的缓冲区,不会像vector一样扩容式要整体迁移旧数据
//2.中控器(map):本质是一个动态数组,存储的是各个缓冲区分首地址(指针数组)当缓冲区数量增加导致中控器装满时,
// 会申请一个更大的中控器(类似 vector 扩容),但只需拷贝旧中控器中存储的 "缓冲区地址",
// 无需迁移缓冲区中的实际数据,效率远高于 vector 扩容。
//
// --deque的适用场景:适合只需头尾操作、无需频繁遍历的场景
//
//-----------------------------"deque常用的接口"---------------------------------------------
//push_fornt() ;在队头插入元素
//push_back() :在队尾插入元素
//pop_front() :删除队头元素
//pop_back() :删除队尾元素
//front() :获取队头元素的引用
//back() :获取队尾元素的引用
//size() :返回元素个数
//empty() :判断是否为空
//clear() :清空所有元素
//operator() :随机访问元素
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void test_deque1()
{
deque<int> dq;
//尾插
dq.push_back(3);
dq.push_back(4);
dq.push_back(5); //3 4 5
//头插
dq.push_front(2);
dq.push_front(1); //1 2 3 4 5
//下标为4(即第五个元素)的元素*5
dq[4] *= 10; //1 2 3 4 50
for (auto e : dq)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_deque1();
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//int main()
//{
// //test_stack1();
// //test_queue1();
// //test_deque1();
// return 0;
//}
🐶 🐾 ✨ 🐾 🐶
- 欢迎留言交流
- 期待你的评论与建议
- 留下你的想法吧

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论

