C++ STL 详解:stack 和 queue 的介绍使用与模拟实现

C++ STL 详解:stack 和 queue 的介绍使用与模拟实现

文章目录

  • [C++ STL 详解:stack 和 queue 的介绍使用与模拟实现](#C++ STL 详解:stack 和 queue 的介绍使用与模拟实现)
    • [stack 的介绍与使用](#stack 的介绍与使用)
      • [1. stack 是什么](#1. stack 是什么)
      • [2. stack 的定义方式](#2. stack 的定义方式)
        • [2.1 使用默认底层容器定义 stack](#2.1 使用默认底层容器定义 stack)
        • [2.2 指定底层容器定义 stack](#2.2 指定底层容器定义 stack)
      • [3. stack 的常用接口](#3. stack 的常用接口)
      • [4. stack 使用示例](#4. stack 使用示例)
    • [queue 的介绍与使用](#queue 的介绍与使用)
      • [1. queue 是什么](#1. queue 是什么)
      • [2. queue 的定义方式](#2. queue 的定义方式)
        • [2.1 使用默认底层容器定义 queue](#2.1 使用默认底层容器定义 queue)
        • [2.2 指定底层容器定义 queue](#2.2 指定底层容器定义 queue)
      • [3. queue 的常用接口](#3. queue 的常用接口)
      • [4. queue 使用示例](#4. queue 使用示例)
    • 容器适配器
      • [1. 什么是容器适配器](#1. 什么是容器适配器)
      • [2. stack 和 queue 为什么叫容器适配器](#2. stack 和 queue 为什么叫容器适配器)
      • [3. 默认底层容器 deque](#3. 默认底层容器 deque)
    • [stack 的模拟实现](#stack 的模拟实现)
      • [1. stack 接口和底层容器的对应关系](#1. stack 接口和底层容器的对应关系)
      • [2. stack 模拟实现代码](#2. stack 模拟实现代码)
      • [3. stack 实现细节说明](#3. stack 实现细节说明)
    • [queue 的模拟实现](#queue 的模拟实现)
      • [1. queue 接口和底层容器的对应关系](#1. queue 接口和底层容器的对应关系)
      • [2. queue 模拟实现代码](#2. queue 模拟实现代码)
      • [3. queue 实现细节说明](#3. queue 实现细节说明)
    • 总结

stack 的介绍与使用

1. stack 是什么

stack 是一种容器适配器,主要用在"后进先出"的场景里

所谓后进先出,也就是 Last In First Out,简称 LIFO

可以把 stack 想成一个只有一端能操作的桶,元素只能从这一端放进去,也只能从这一端拿出来

比如按顺序压入 1、2、3、4,那么取出来的时候顺序就是 4、3、2、1

stack 的特点可以这样记:

  1. 元素只能从栈顶进入
  2. 元素也只能从栈顶出去
  3. 最后放进去的元素最先出来
  4. 不支持遍历
  5. 不支持随机访问
  6. 只暴露栈这种数据结构需要的接口

stack 常见使用场景:

  1. 函数调用栈
  2. 表达式求值
  3. 括号匹配
  4. 递归转非递归
  5. 深度优先搜索 DFS
  6. 浏览器后退记录这类"最近操作优先处理"的场景

使用 stack 需要包含头文件:

cpp 复制代码
#include <stack>

2. stack 的定义方式

stack 是容器适配器,它自己不直接管理底层存储,而是包装其他容器来使用

2.1 使用默认底层容器定义 stack
cpp 复制代码
stack<int> st1;

这种写法最常见

如果没有指定底层容器,标准库里的 stack 默认使用 deque 作为底层容器

也就是说:

cpp 复制代码
stack<int> st1;

大致可以理解成:

cpp 复制代码
stack<int, deque<int>> st1;
2.2 指定底层容器定义 stack

stack 的第二个模板参数可以指定底层容器

比如使用 vector:

cpp 复制代码
stack<int, vector<int>> st2;

也可以使用 list:

cpp 复制代码
stack<int, list<int>> st3;

完整示例:

cpp 复制代码
#include <iostream>
#include <stack>
#include <vector>
#include <list>
using namespace std;

int main()
{
    stack<int> st1;              // 默认使用 deque 作为底层容器
    stack<int, vector<int>> st2; // 指定 vector 作为底层容器
    stack<int, list<int>> st3;   // 指定 list 作为底层容器

    st1.push(1);
    st2.push(2);
    st3.push(3);

    cout << st1.top() << endl;
    cout << st2.top() << endl;
    cout << st3.top() << endl;

    return 0;
}

注意一点:stack 要求底层容器至少能支持 back、push_back、pop_back、size、empty 这些操作

所以 vector、deque、list 都能作为 stack 的底层容器

3. stack 的常用接口

stack 常用成员函数如下:

成员函数 作用
empty 判断栈是否为空
size 获取栈中有效元素个数
top 获取栈顶元素
push 元素入栈
pop 元素出栈
swap 交换两个栈中的数据

这些接口可以按栈顶来理解:

  1. push 往栈顶放数据
  2. pop 从栈顶删数据
  3. top 查看栈顶数据
  4. empty 判断有没有数据
  5. size 看当前有多少个数据
  6. swap 交换两个 stack 内部维护的数据

注意:pop 只负责删除栈顶元素,不会返回被删除的值

如果想拿到被删除的数据,要先 top,再 pop

cpp 复制代码
int x = st.top();
st.pop();

4. stack 使用示例

cpp 复制代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main()
{
    // 指定 vector 作为 stack 的底层容器
    stack<int, vector<int>> st;

    // 入栈顺序:1 2 3 4
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);

    cout << st.size() << endl; // 4

    // stack 是后进先出,所以输出顺序是 4 3 2 1
    while (!st.empty())
    {
        cout << st.top() << " "; // 先看栈顶元素
        st.pop();                // 再删除栈顶元素
    }

    cout << endl;

    return 0;
}

输出结果:

text 复制代码
4
4 3 2 1

这里入栈顺序是 1、2、3、4,最后进入的是 4,所以 4 最先出来

queue 的介绍与使用

1. queue 是什么

queue 也是一种容器适配器,主要用在"先进先出"的场景里

所谓先进先出,也就是 First In First Out,简称 FIFO

可以把 queue 想成排队买票,先排队的人先买票离开,后排队的人后处理

queue 的特点可以这样记:

  1. 元素从队尾进入
  2. 元素从队头出去
  3. 最先进入队列的元素最先出来
  4. 不支持随机访问
  5. 不支持遍历
  6. 只暴露队列需要的接口

queue 常见使用场景:

  1. 广度优先搜索 BFS
  2. 任务调度
  3. 消息队列
  4. 缓冲区
  5. 排队模型
  6. 按到达顺序处理请求的场景

使用 queue 需要包含头文件:

cpp 复制代码
#include <queue>

2. queue 的定义方式

2.1 使用默认底层容器定义 queue
cpp 复制代码
queue<int> q1;

如果没有指定底层容器,标准库里的 queue 默认也使用 deque

2.2 指定底层容器定义 queue

queue 也可以指定底层容器

cpp 复制代码
queue<int, list<int>> q2;

也可以看到这种写法:

cpp 复制代码
queue<int, deque<int>> q3;

需要注意的是,queue 的底层容器要支持 front、back、push_back、pop_front、size、empty 等操作

所以 deque 和 list 很适合作为 queue 的底层容器

vector 一般不适合作为 queue 的底层容器,因为 queue 出队要从队头删除,需要底层容器支持 pop_front,而 vector 没有 pop_front

所以像下面这种写法在标准库里通常是不能正常满足 queue 适配要求的:

cpp 复制代码
// 不推荐,也通常不符合 queue 对底层容器的要求
queue<int, vector<int>> q;

如果只是学习模板参数可以这么看,但真正写代码时,queue 常用 deque 或 list

完整示例:

cpp 复制代码
#include <iostream>
#include <queue>
#include <list>
using namespace std;

int main()
{
    queue<int> q1;            // 默认使用 deque
    queue<int, list<int>> q2; // 指定 list 作为底层容器

    q1.push(10);
    q2.push(20);

    cout << q1.front() << endl;
    cout << q2.front() << endl;

    return 0;
}

3. queue 的常用接口

queue 常用成员函数如下:

成员函数 作用
empty 判断队列是否为空
size 获取队列中有效元素个数
front 获取队头元素
back 获取队尾元素
push 队尾入队列
pop 队头出队列
swap 交换两个队列中的数据

这些接口可以按队头和队尾来理解:

  1. push 往队尾插入数据
  2. pop 从队头删除数据
  3. front 查看队头数据
  4. back 查看队尾数据
  5. empty 判断队列是否为空
  6. size 查看队列中有多少个元素
  7. swap 交换两个队列中的数据

注意:queue 的 pop 也只删除元素,不返回元素

如果想拿到即将出队的数据,要先 front,再 pop

cpp 复制代码
int x = q.front();
q.pop();

4. queue 使用示例

cpp 复制代码
#include <iostream>
#include <list>
#include <queue>
using namespace std;

int main()
{
    // 指定 list 作为 queue 的底层容器
    queue<int, list<int>> q;

    // 入队顺序:1 2 3 4
    q.push(1);
    q.push(2);
    q.push(3);
    q.push(4);

    cout << q.size() << endl; // 4

    // queue 是先进先出,所以输出顺序是 1 2 3 4
    while (!q.empty())
    {
        cout << q.front() << " "; // 查看队头元素
        q.pop();                  // 删除队头元素
    }

    cout << endl;

    return 0;
}

输出结果:

text 复制代码
4
1 2 3 4

这里入队顺序是 1、2、3、4,最先进队的是 1,所以 1 最先出队

容器适配器

1. 什么是容器适配器

stack 和 queue 都能存放元素,但 STL 并没有把它们当成普通容器,而是把它们叫作容器适配器

适配器这个名字可以这么理解:它本身不重新造一套完整的数据结构,而是在已有容器的基础上包装一层接口,让这个容器表现出某种特定的数据结构行为

比如 stack 想要的是后进先出,所以它只暴露 push、pop、top 等接口

底层容器明明可能有迭代器、下标、insert 等能力,但 stack 不把这些接口暴露出来

queue 也是一样,它只暴露队列需要的 push、pop、front、back 等接口

2. stack 和 queue 为什么叫容器适配器

学过数据结构后会知道,栈和队列既可以用顺序表实现,也可以用链表实现

在 STL 中,stack 和 queue 就是把某个现成容器包装起来,然后提供栈或队列的操作方式

比如定义:

cpp 复制代码
stack<int, vector<int>> st;

可以理解成:这个 stack 内部用 vector 存数据,但对外只给你栈的接口

再比如:

cpp 复制代码
queue<int, list<int>> q;

可以理解成:这个 queue 内部用 list 存数据,但对外只给你队列的接口

所以 stack 和 queue 的类模板一般有两个模板参数:

  1. 第一个模板参数是元素类型
  2. 第二个模板参数是底层容器类型

也就是类似这样的形式:

cpp 复制代码
template<class T, class Container = deque<T>>
class stack;

template<class T, class Container = deque<T>>
class queue;

当不指定第二个模板参数时,它们默认使用 deque

3. 默认底层容器 deque

stack 和 queue 默认使用 deque,是因为 deque 同时支持头部和尾部的高效操作

对于 stack 来说,它主要需要:

  1. push_back
  2. pop_back
  3. back
  4. size
  5. empty

deque 支持这些接口

对于 queue 来说,它主要需要:

  1. push_back
  2. pop_front
  3. front
  4. back
  5. size
  6. empty

deque 也支持这些接口

所以 deque 很适合作为二者的默认底层容器

stack 的模拟实现

1. stack 接口和底层容器的对应关系

stack 的模拟实现比较直接,因为它只是对底层容器接口做了一层包装

可以先把 stack 的接口和底层容器接口对应起来:

stack 接口 作用 底层容器调用
push 元素入栈 push_back
pop 元素出栈 pop_back
top 获取栈顶元素 back
size 获取栈中有效元素个数 size
empty 判断栈是否为空 empty
swap 交换两个栈中的数据 swap

这里选择模板参数 Container 作为底层容器类型

如果用户不指定 Container,就默认用 std::deque

2. stack 模拟实现代码

cpp 复制代码
#pragma once

#include <deque>
#include <cstddef>

namespace cl
{
    // stack 是容器适配器,底层默认使用 deque
    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 对象只能读
        const T& top() const
        {
            return _con.back();
        }

        // 获取栈中有效元素个数
        size_t size() const
        {
            return _con.size();
        }

        // 判断栈是否为空
        bool empty() const
        {
            return _con.empty();
        }

        // 交换两个 stack 的底层容器
        void swap(stack<T, Container>& st)
        {
            _con.swap(st._con);
        }

    private:
        Container _con; // 真正存储数据的底层容器
    };
}

3. stack 实现细节说明

这个 stack 的实现重点在于:栈顶放在底层容器的尾部

这样做的原因是,大多数顺序容器和链表容器都支持尾插和尾删

push 的时候调用 push_back,相当于把元素压到栈顶

pop 的时候调用 pop_back,相当于把栈顶元素删除

top 的时候调用 back,拿到的就是最后一个元素,也就是栈顶元素

这个实现不提供迭代器,也不提供下标访问,因为 stack 只关心栈顶

如果底层容器是 vector:

cpp 复制代码
cl::stack<int, std::vector<int>> st;

那么 push、pop、top 最终都会转成 vector 的 push_back、pop_back、back

如果底层容器是 list:

cpp 复制代码
cl::stack<int, std::list<int>> st;

那么这些操作最终会转成 list 的 push_back、pop_back、back

queue 的模拟实现

1. queue 接口和底层容器的对应关系

queue 的模拟实现也很直接,本质上也是包装底层容器的接口

queue 的接口和底层容器接口对应关系如下:

queue 接口 作用 底层容器调用
push 队尾入队列 push_back
pop 队头出队列 pop_front
front 获取队头元素 front
back 获取队尾元素 back
size 获取队列中有效元素个数 size
empty 判断队列是否为空 empty
swap 交换两个队列中的数据 swap

queue 和 stack 最大的区别在于:

  1. stack 只在一端进出
  2. queue 从队尾进,从队头出

所以 queue 的底层容器必须支持 pop_front

2. queue 模拟实现代码

cpp 复制代码
#pragma once

#include <deque>
#include <cstddef>

namespace cl
{
    // queue 是容器适配器,底层默认使用 deque
    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 对象只能读
        const T& front() const
        {
            return _con.front();
        }

        // 获取队尾元素,普通对象可读可写
        T& back()
        {
            return _con.back();
        }

        // 获取队尾元素,const 对象只能读
        const T& back() const
        {
            return _con.back();
        }

        // 获取队列中有效元素个数
        size_t size() const
        {
            return _con.size();
        }

        // 判断队列是否为空
        bool empty() const
        {
            return _con.empty();
        }

        // 交换两个 queue 的底层容器
        void swap(queue<T, Container>& q)
        {
            _con.swap(q._con);
        }

    private:
        Container _con; // 真正存储数据的底层容器
    };
}

3. queue 实现细节说明

queue 的实现重点在于队头和队尾分工

push 调用 push_back,让新元素进入底层容器尾部

pop 调用 pop_front,让队头元素离开队列

front 调用 front,访问队头元素

back 调用 back,访问队尾元素

因为 queue 需要 pop_front,所以底层容器不能随便选

deque 支持 pop_front,所以适合

list 也支持 pop_front,所以也适合

vector 不支持 pop_front,所以不适合作为标准 queue 的底层容器

总结

stack 和 queue 都不是普通意义上的容器,而是容器适配器

它们的核心思想是:把已有容器包起来,只暴露符合栈或队列语义的接口

stack 重点记这些:

  1. 后进先出
  2. 默认底层容器是 deque
  3. 可以指定 vector、list、deque 等合适容器作为底层容器
  4. push 对应底层容器的 push_back
  5. pop 对应底层容器的 pop_back
  6. top 对应底层容器的 back
  7. pop 不返回元素,想获取元素要先 top 再 pop

queue 重点记这些:

  1. 先进先出
  2. 默认底层容器是 deque
  3. 常用底层容器是 deque 或 list
  4. push 对应底层容器的 push_back
  5. pop 对应底层容器的 pop_front
  6. front 对应队头元素
  7. back 对应队尾元素
  8. pop 不返回元素,想获取元素要先 front 再 pop

模拟实现时,stack 和 queue 本身没有太复杂的底层逻辑

真正干活的是内部的底层容器,stack 和 queue 只是把接口重新包装了一下

所以理解它们的关键,不是背接口,而是明白:

  1. stack 限制了"只能从一端进出"
  2. queue 限制了"从一端进,从另一端出"
  3. 容器适配器的价值就在于隐藏底层容器多余的能力,只留下符合数据结构语义的操作
相关推荐
油炸自行车1 小时前
【bug】Qt 6 Q_NAMESPACE 跨 DLL 链接错误:LNK2019 无法解析 staticMetaObject
数据库·c++·qt·bug·link2019·q_namespace_exp·namespaceexport
插件开发1 小时前
英伟达cuda程序通用性关键 geforce 20xx代到最新版 在20xx上编译的c++程序可以通用吗?
java·c++·人工智能
BestOrNothing_20151 小时前
ROS2 C++ 小车控制完整实战(三):自定义 srv 服务通信保姆级教程
c++·service通信·ros2·client·server·srv
KuaCpp1 小时前
C++进阶(上)
linux·c++
草莓熊Lotso1 小时前
【Linux网络】深入理解 TCP 协议(一):报头设计与可靠性基石
linux·运维·服务器·c语言·网络·c++·tcp/ip
加油码1 小时前
Linux 信号详解:从 Ctrl+C 到进程异常退出,真正理解信号机制
linux·服务器·c++
Shadow(⊙o⊙)2 小时前
QT常用控件3.0,font字体设置,toolTip提示,focusPolicy焦点定位原则,中型控件StyleSheet样式表。
服务器·开发语言·前端·c++·qt
Shadow(⊙o⊙)2 小时前
QT常用控件2.0,windowOpacity窗口透明度,Cursor光标设置
开发语言·c++·qt
Lazionr2 小时前
类和对象(上):走进面向对象编程
c++