C++ STL 容器深度解析:deque 结构剖析与 priority_queue 的核心原理及模拟实现

目录

  • 前言
  • 一、deque的结构剖析
    • [1.1 deque的优缺点](#1.1 deque的优缺点)
  • 二、priority_queue(优先级队列)
    • [2.1 核心定义](#2.1 核心定义)
    • [2.2 底层结构与核心依赖](#2.2 底层结构与核心依赖)
    • [2.3 核心特性](#2.3 核心特性)
    • [2.4 关键用法:自定义优先级(小顶堆 / 自定义规则)](#2.4 关键用法:自定义优先级(小顶堆 / 自定义规则))
  • 三、priority_queue的模拟实现
    • [3.1 向上调整建大堆](#3.1 向上调整建大堆)
      • [3.1.1 ADL(参数依赖查找)](#3.1.1 ADL(参数依赖查找))
    • [3.2 pop() + 向下调整建大堆](#3.2 pop() + 向下调整建大堆)
    • [3.3 迭代器区间初始化 + 向下调整建大堆](#3.3 迭代器区间初始化 + 向下调整建大堆)
    • [3.4 size](#3.4 size)
  • 四、完整源码
  • 结语

🎬 云泽Q个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列

⛺️遇见安然遇见你,不负代码不负卿~


前言

大家好啊,我是云泽Q ,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~

本文是上一篇文章的续写,结合上一篇文章来看体验更佳STL容器性能探秘:stack、queue、deque的实现与CPU缓存命中率优化

一、deque的结构剖析

deque就是一个被设计出来用来尝试兼容二者优点的数据结构,但是最终从结论来看deque是一个半成品

链表一个小空间就存一个数据优点浪费,deque的结构就是由一个个的小数组buffer构成,这个小数组的大小由开发者自己定义,存10个或8个数据都可以,当第一个buffer存满了之后不进行扩容,再开一个buffer,以此类推,buffer之间是用一个中控数组(指针数组)来管理,中控数组中存有一些指针指向这些buffer

这样vector的缺点:扩容的性能消耗就没有了,空间浪费还有,但是不多,就算一个buffer可以存一个数据,只存1个数据的话也最多浪费9个,CPU高速缓存访问命中率也不错,一个buffer一次存几十个字节,访问第一个数据后面的数据也会加载进去,且支持下标的随机访问。

相比list,deque的底层是连续空间,访问数据的时候计算出上图的x,和y也可以直接访问数据,不用像lis是单向的,需要从前往后遍历找

deuqe其实也是存在扩容的,比如说中控数组满了也是需要扩容的,但是该扩容代价就很低(只需要拷贝指针),vector扩容若存的数据类型是string,就需要拷贝string,代价很高

下面再继续分析他的结构

库中deque的迭代器设计比较复杂,string,vector的迭代器是直接对原生指针进行typedef,list的迭代器是对节点的指针用类进行封装,deque的迭代器是用四个指针进行封装(因为要跨缓冲区遍历)

cur:指向当前缓冲区中正在访问的元素 (如图中迭代器的cur指向某个元素);

first:指向当前缓冲区的起始位置 (比如第一个缓冲区的0);

last:指向当前缓冲区的末尾位置 (比如第一个缓冲区的7);

node(二级指针):指向中控器中当前缓冲区对应的指针 (即中控器指针数组里的某个元素)。

元素的存储逻辑 :push_back(尾插)会往最后一个缓冲区的末尾 添加元素,若该缓冲区已满,则新建一个缓冲区并挂到中控器;push_front(头插)会往第一个缓冲区的开头 添加元素,若该缓冲区已满,则新建缓冲区挂到中控器(原第一个buffer不是从中控数组的开始位置开始放的,而是差不多放在一个中间位置,这样的优点就是头插不需要对中控数组马上扩容的,方便头插)。

迭代器的遍历逻辑 (对应图左侧的循环auto it = begin(); while(it != end()) {*it; ++it;}):

begin()返回start迭代器(一个临时的deque<int>::iterator对象);

调用迭代器的拷贝构造函数__deque_iterator的拷贝构造是浅拷贝),将临时对象的 4 个成员(cur/first/last/node)依次赋值给it的对应成员;

拷贝完成后,it成为一个独立的迭代器对象,但它的 4 个成员指针和start指向相同的内存地址(比如it.cur = start.cur = &buf0[0])。

cpp 复制代码
迭代器的浅拷贝是安全的:因为迭代器只是 "指向数据的工具",不管理内存,拷贝仅复制指针值,不会导致数据重复或内存泄漏。
  • 当++it时,先让cur指向当前缓冲区的下一个元素;
  • 若cur到达last(当前缓冲区末尾),则通过node(++node)找到中控器里下一个缓冲区的指针,然后将first/last切换为新缓冲区的起止,cur指向新缓冲区的first(实现跨缓冲区遍历);
  • begin()对应的是start迭代器:cur指向第一个缓冲区的第一个元素,node指向中控器第一个缓冲区的指针;
  • end()对应的是finish迭代器:cur指向最后一个缓冲区的下一个位置(不是实际元素),node指向中控器最后一个缓冲区的指针。

结合图中push_back操作看deque的扩容

图右侧的ideq::push_back(0); push_back(1); push_back(2);展示了尾插逻辑:

  • 尾插会优先往最后一个缓冲区的末尾添加元素;
  • 若最后一个缓冲区已满(比如图中前两个缓冲区07、815已存满),则新建一个缓冲区(图中第三个16~23),并在中控器的指针数组中添加指向该缓冲区的指针;
  • 新元素(0、1、2)被存入新缓冲区的起始位置(或末尾,取决于缓冲区是否为空)。

补充:图中map是一个指向中控数组的指针(二级指针)

这是对应源码的核心逻辑,可以对照看一下

并且还有一些情况不能直接除和模,比如要访问第12个数据,前提是前面每个buffer是存满的,像上上图第一个buffer存了两个数据(不满),要把第一个buffer的数据个数减掉,然后再来计算在第几个buffer,在第几个buffer中的哪个位置,要进行好几步计算

1.1 deque的优缺点

一、deque 的核心优点

  1. 头尾插入 / 删除操作高效(时间复杂度 O (1))
    • 原因:deque的头尾操作 仅需操作第一个 / 最后一个缓冲区的头尾位置 (若缓冲区未满,直接插入 / 删除;若缓冲区已满,只需新增一个缓冲区并挂到中控器,无需移动其他元素)。很适合做stack和queue的默认适配容器
    • 对比:
      优于vector:vector尾插虽 O (1) 但扩容时需拷贝整块内存,头插则需移动所有元素(O (n));
      优于list:list头尾操作也是 O (1),但deque单缓冲区内连续存储,缓存效率更高。
  1. 动态扩容成本极低
    • 原因:deque扩容是新增独立的分段缓冲区,只需在中控器的指针数组中添加新缓冲区的地址,无需像vector那样拷贝原有的所有元素(vector扩容的拷贝成本是 O (n))。
    • 场景:当需要频繁动态扩展容器大小时,deque的扩容开销远低于vector。
  1. 单缓冲区内连续存储,缓存命中率远高于list
    原因:deque的每个缓冲区是连续内存,访问缓冲区内的元素时,能利用 CPU 缓存的 "局部性原理"(一次加载整个缓存行),避免list分散节点导致的频繁缓存未命中。
    实际表现:遍历deque的效率远高于list,接近vector的遍历效率。
  2. 支持随机访问(时间复杂度 O (1))
    原因:通过中控器的指针数组,可快速定位元素所在的缓冲区(计算map的索引),再通过缓冲区的偏移量找到目标元素(buf[offset])。
    对比:list不支持随机访问(需遍历节点,O (n)),deque的随机访问能力比list更灵活。

二、deque 的核心缺点

  1. 中间位置的插入 / 删除效率低(时间复杂度 O (n))
    • 原因:中间操作需要移动 "插入 / 删除位置之后的所有元素"------ 若元素跨多个缓冲区,需移动多个缓冲区内的元素(而list中间操作是 O (1),仅需调整指针)。
    • 场景:若业务需频繁在容器中间操作,deque远不如list高效。
  1. 随机访问效率低于vector
    • 原因:deque的随机访问需经过 "中控器找缓冲区指针→缓冲区内部找偏移" 两步跳转,而vector是直接的内存地址偏移 (仅需base_ptr + index),硬件层面更高效。
    • 实际表现:deque的[]操作虽然是 O (1),但实际执行速度比vector慢 2~3 倍。

      这里是用STL中的sort间接体现 deque 的随机访问效率劣势,STL 的sort的核心要求是容器支持随机访问迭代器 ,且排序过程中会频繁执行随机访问操
      而vector和deque的随机访问底层逻辑差异,会被sort的高频随机访问放大,最终体现在耗时上

      拷贝到vector排序都快一筹
  1. 代器复杂且操作成本高
    • 原因:deque的迭代器是 "智能指针类型"(包含cur/first/last/node四个成员),++it/--it等操作需判断是否跨缓冲区(涉及node跳转、first/last更新),而vector的迭代器是普通指针(操作仅需简单地址偏移)。
    • 限制:deque迭代器不支持 "随意加减法"(如it += 5)------ 需逐次判断跨缓冲区,效率远低于vector迭代器。
  1. 存在内存碎片化风险
    原因:deque由多个独立的分段缓冲区组成,虽然单个缓冲区是连续的,但整体内存是分散的(不像vector是整块连续内存),长期频繁扩容可能导致更多内存碎片。

二、priority_queue(优先级队列)

2.1 核心定义

priority_queue没有单独的头文件,其包含在queue这个头文件中

priority_queue也是一个容器适配器,其核心特性是:内部元素会自动按 "优先级" 排序,每次访问 / 弹出的都是优先级最高的元素(默认是 "值越大优先级越高")。

底层本质是:基于「随机访问容器(默认vector)」+「堆排序算法」实现,本质是一棵完全二叉树结构的堆(默认大顶堆)。

其模板还有一个参数是仿函数,后面我会出一篇文章单独来写,其接口的含义与之前容器也有些区别

2.2 底层结构与核心依赖

  1. 容器适配器特性
    priority_queue 依赖底层容器存储数据,满足两个要求:
    • 底层容器必须支持随机访问(需[ ]/at()或随机访问迭代器);
    • 支持push_back()/pop_back()/size()/empty()等接口。
      默认底层容器是vector,也可指定deque(但不能用 list------list 不支持随机访问,堆算法无法执行)。
  1. 堆算法的支撑
    STL 通过make_heap(构建堆)、push_heap(插入元素调整堆)、pop_heap(弹出元素调整堆)等算法,基于底层容器的连续内存,维护堆的结构:
    • 大顶堆(默认):根节点(队首)是最大值,每个父节点值≥子节点;
    • 小顶堆(自定义):根节点是最小值,每个父节点值≤子节点。

2.3 核心特性

  1. 优先级自动排序:插入元素时,容器会自动调整堆结构,保证队首始终是优先级最高的元素(无需手动排序);
  2. 访问限制 :仅能访问队首(优先级最高)元素,不支持随机访问、不支持遍历(无begin()/end()迭代器);
  3. 时间复杂度
    • push()(插入元素):O (logn)(堆的上浮调整);
    • pop()(弹出队首):O (logn)(堆的下沉调整);
    • top()(获取队首):O (1)(直接访问堆顶);
    • 构建堆(初始化):O (n)。

2.4 关键用法:自定义优先级(小顶堆 / 自定义规则)

默认是 "大顶堆",若需调整优先级,需指定「底层容器」+「比较规则」:

库里面实现的两个仿函数的类型,仿函数就是重载了一个运算符operator(),其就可以像函数一样去使用,仿照函数功能的一个类,有些书上也被叫做函数对象(这个类的对象具有像函数一样去调用的功能)

补充一点:主流的容器适配器(stack/queue/priority_queue)为了保证自身数据结构的访问规则,是刻意不暴露迭代器接口的

  1. stack/queue:完全不暴露迭代器(STL 标准实现)
    这两个适配器的设计目标是严格遵循固定的访问规则:
    • stack:仅支持 "后进先出(LIFO)",只能访问 / 操作栈顶元素;
    • queue:仅支持 "先进先出(FIFO)",只能访问 / 操作队首 / 队尾元素。

如果暴露迭代器(begin()/end()),用户可以遍历、修改任意位置的元素,直接打破stack/queue的核心访问规则(比如stack本应只能操作栈顶,却能通过迭代器修改栈底元素),违背其设计初衷。

  1. priority_queue:同样不暴露迭代器
    priority_queue的设计目标是 "仅能访问优先级最高的堆顶元素",其底层依赖堆结构(完全二叉树):
    • 若暴露迭代器,用户可能直接修改堆中任意元素,破坏堆的结构(堆的有序性依赖严格的父 / 子节点关系),导致priority_queue的优先级规则失效;
    • 因此 STL 中priority_queue无迭代器接口,仅提供top()访问堆顶、push()/pop()调整堆结构。

三、priority_queue的模拟实现

3.1 向上调整建大堆

这里有的在用swap可能会遇到各种各样的问题,我结合我所遇到的问题把该过程的原因梳理一下:

在调试代码时我发现,在包含iostream和algorithm头文件的时候,使用swap只要不加st::就会报错,且无论包含iostream,algorithm,程序都可以使用std::swap,原因如下:

3.1.1 ADL(参数依赖查找)

一、 核心原因是 ADL(参数依赖查找)规则 + 内置类型的特性

  1. 调用未指定命名空间的函数(如 swap(a,b))时,编译器先在「当前命名空间」(如上述代码的 yunze)和「全局命名空间」查找;
  2. 若未找到,会去「参数 a/b 的类型所属的命名空间」补充查找;
  • 代码中 swap 的参数是 std::vector< int > 的元素(类型为 int),int 是内置类型,仅归属于全局命名空间,不属于 std 命名空间;
  • 当写 swap(...) 时,编译器只会按以下顺序查找:
    • 第一步:当前命名空间(yunze)------ 没有定义 swap;
    • 第二步:全局命名空间 ------ 没有定义 swap;
    • 第三步:参数类型所属的命名空间(仅全局)------ 仍无 swap;

即便 < iostream >/< algorithm > 让 std 命名空间中存在 std::swap,但 ADL 规则不会让编译器去 std 命名空间找 "未加前缀的 swap",因此必然报错。当参数是 std 命名空间下的类型(如 std::string、std::vector 对象),ADL 才会自动去 std 里找 swap,不用加 std::。

二、为什么包含 < iostream >/< algorithm >,用 std::swap 就可以?

核心原因是 这两个头文件都间接包含了 < utility >

  1. std::swap 的官方定义在 < utility > 头文件中;
  2. 主流编译器(GCC/MSVC/Clang)的:
    • < iostream >:内部依赖 < utility >(处理输入输出的字符串 / 缓冲区等),会间接包含它;
    • < algorithm >:内部很多算法(如排序)需要用到 std::swap,也会间接包含 < utility >;
  1. 因此,只要包含这两个头文件之一,编译器就能找到 std 命名空间中 std::swap 的定义,调用 std::swap 自然编译通过。

添加 using namespace std; 后,调用 swap 时不用加 std:: 前缀也能编译通过,核心原因是这条语句的本质是将 std 命名空间的所有标识符 "导入" 到当前作用域,让编译器能直接找到 std::swap。

一、using namespace std; 的核心作用

using namespace std; 是 C++ 的命名空间使用指令,它的本质是:

告诉编译器:"在当前作用域中,查找未指定命名空间的标识符时,除了查找当前命名空间、全局命名空间,还要自动去 std 命名空间查找。"

简单说,这条语句相当于给 std 命名空间的所有内容(比如 swap、vector、cout 等)"去掉了 std:: 前缀",让你可以直接使用这些标识符。

二**、结合场景:加了 using namespace std; 后,swap 能被找到的全过程**

还是以 T=int 的代码为例,添加 using namespace std; 后,编译器查找 swap(_con[child], _con[parent]) 的步骤变成了:

  1. 第一步:查找当前命名空间(yunze)→ 没有 swap;
  2. 第二步:查找全局命名空间 → 没有 swap;
  3. 第三步:因为加了 using namespace std;,编译器额外查找 std 命名空间 → 找到 std::swap;
  4. 编译器确认 std::swap 匹配参数类型(int),调用成功,编译通过。

对比没加 using namespace std; 的情况:编译器只走前两步,找不到 swap 就报错;加了之后多了第三步,能找到 std::swap

3.2 pop() + 向下调整建大堆

删除数据要做向下调整,是不能在数组中直接挪动后面的数据来覆盖前面的数据。
原因有二

  1. 对于vector这样的容器,向前挪动数据效率很低
  2. 且挪动数据之后,结构就全乱了,要重新建堆,而建堆的代价又是O(n)

所以最佳方式就是不要动大结构,收尾数据交换,只后再进行尾删,vector尾删的效率很高

删除之后左右子树都是大堆,要进行向下调整

3.3 迭代器区间初始化 + 向下调整建大堆

迭代器区间初始化栈和队列没有支持,但是优先级队列支持了

这里也可以遍历first和last之间的的值,直接调用push,但push就是向上调整和建堆了,我二叉树的文章深入理解二叉树与堆:结构、实现与典型应用有写过,向上调整建堆的效率是没有向下调整建堆高的,所以说这里不建议使用遍历调用push的方法,而是找到最后一个不是叶子的节点,从右向左,从下向上采用向下调整建大堆的方法

详细解释一下这里的代码:
InputIterator是一个遵循 C++ 标准库的命名习惯、用来表示 "输入迭代器 " 模板参数的名称,用它作为名字,是为了表明这个构造函数接收的参数是 "输入迭代器类型"(符合输入迭代器的行为要求,不一定是特定类型,只要某个类型满足迭代器的核心行为,就可以被当作迭代器使用

cpp 复制代码
"输入迭代器" 的要求:
支持 ++ 操作(单向移动,指向后一个元素);
支持 * 解引用(读取指向的元素,不能只写);
支持 != 比较(判断是否遍历到区间末尾);
支持拷贝构造 / 赋值(比如 It it = first;)

由于这里指向数组元素地址的指针(比如 int*),刚好匹配迭代器行为,相当于一个容器迭代器

cpp 复制代码
++p:指针移动到下一个元素(比如 int* 指针 ++,地址 + 4,指向数组中下一个 int);
*p:解引用指针,读取数组元素;
p != q:比较两个指针的地址,判断是否遍历到末尾;
指针本身就是基础类型,天然支持拷贝、赋值。

容器迭代器的行为

cpp 复制代码
容器迭代器是标准库专门设计的类型,天生就是为了满足迭代器行为:
++it:迭代器移动到下一个元素(底层由容器实现,比如 vector 迭代器本质是封装了指针,++ 就是指针移动);
*it:读取迭代器指向的元素;
it != end():判断是否遍历到末尾;
支持拷贝、赋值等基础操作。

模板参数也是可以轻松 "兼容" 两种类型的
_con(first, last) 里,容器的区间构造函数会遍历 [first, last),而遍历只需要 ++、*、!= 这几个行为)

所以:

  • vector::iterator:编译器推导 InputIterator = vector::iterator,遍历用迭代器的 ++/*
  • int*:编译器推导 InputIterator = int*,遍历用指针的 ++/*
  • 两种情况都能正常执行 _con(first, last),因为容器的区间构造函数本身就支持 "任意满足输入迭代器行为的类型"。

补充一下:构造函数里的 _con(first, last) 是直接调用容器(比如 std::vector)的区间构造函数,容器会自动遍历 [first, last) 区间,把所有元素拷贝到 _con 中

cpp 复制代码
priority_queue() = default;

再说一下这行代码的作用:

一、这句代码的核心作用:显式要求编译器生成 "默认构造函数"

默认构造函数是指不需要传任何参数就能调用的构造函数 (比如 priority_queue< int > pq; 这种无参创建对象的方式,就会调用默认构造)。

C++ 的规则是:

  • 如果你的中没有显式定义任何构造函数,编译器会自动生成一个默认构造函数(做一些基础初始化,比如前面_con 会被 std::vector 的默认构造初始化);
  • 但如果类中显式定义了其他构造函数(比如代码里的 "迭代器区间构造函数"),编译器就不会再自动生成默认构造函数

priority_queue() = default; 是 C++11 引入的语法,作用是:

显式告诉编译器:"我需要你帮我生成一个默认的无参构造函数",功能和编译器自动生成的默认构造完全一致(比如初始化 _con 为空的 std::vector)。

由于代码中已经显式定义了 "迭代器区间构造函数"(priority_queue(InputIterator first, InputIterator last)),所以编译器不会自动生成默认构造函数。

此时如果尝试无参创建 priority_queue 对象(比如 main 函数里注释掉的 yunze::priority_queue< int > pq;),编译器会报错。

完整的初始化流程(无参创建对象时)

  1. 调用 priority_queue 的默认构造函数(由 priority_queue() = default; 让编译器生成);
  2. 编译器生成的默认构造函数会执行「逐成员初始化」------ 对每个成员变量,调用其自身的默认构造函数;
  3. 成员变量 _con 是 std::vector< T > 类型,因此会调用 std::vector 的无参构造函数,初始化一个空的 vector(size=0,底层无内存分配);
  4. 最终 pq 成为一个空的优先级队列(pq.empty() 返回 true)。

如果手动写默认构造,效果完全一致

cpp 复制代码
// 和 priority_queue() = default; 功能完全一致
priority_queue()
{
    // 空函数体------编译器会自动在这里调用 _con 的无参构造
}

之所以用 = default;,是因为它更简洁,且能让编译器生成 "更高效的默认构造函数"(和编译器自动生成的原生默认构造完全一致)。

3.4 size

四、完整源码

queue.h

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

namespace yunze
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		//队尾入数据
		void push(const T& x)
		{
			//Container若不支持push_back就会报错,说明容器不适配
			_con.push_back(x);
		}

		//队头出数据
		void pop()
		{
			_con.pop_front();
		}
		
		//取队头数据
		const T& front()
		{
			
			return _con.front();
		}

		//取队尾数据
		const T& back()
		{

			return _con.back();
		}

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

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

	private:
		//vector<T> _v;
		Container _con;
	};
}

stack.h

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

namespace yunze
{
	//template<class T>
	//class stack //数组栈
	//{
	//	// ...
	//private:
	//	T* _a;
	//	size_t _top;
	//	size_t _capacity;
	//};
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		//构造析构拷贝不用写,没有其他需求,默认生成的够用
		void push(const T& x)
		{
			//Container若不支持push_back就会报错,说明容器不适配
			_con.push_back(x);
		}

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

		//top这里不用[],因为不一定是vector
		const T& top()
		{
			//容器一般都有一个front和back接口,如vector和list
			return _con.back();
		}

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

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

	private:
		//vector<T> _v;
		Container _con;
	};
}

priority_queue.h

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

namespace yunze
{
	template<class T, class Container = std::vector<T>>
	class priority_queue
	{
	public:
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}

		//强制编译器生成默认构造
		priority_queue() = default;

		void adjust_up(int child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child] < _con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else {
					break;
				}
			}
		}

		void adjust_down(int parent)
		{
			//假设法,默认左孩子大
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child] > _con[child + 1])
				{
					child++;
				}
				if (_con[child] < _con[parent])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else {
					break;
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		void pop()
		{
			//用front和back也可以,容器一般都支持这两个接口
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top() const
		{
			return _con[0];
		}

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

		size_t size() const
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

test.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 666
//#include"stack.h"

//int main()
//{
//	//yunze::stack<int, vector<int>> st;//数组栈
//	//yunze::stack<int, list<int>> st;//数组栈
//	yunze::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;
//	return 0;
//}

//#include"queue.h"

////默认也是用deque适配
//int main()
//{
//	yunze::queue<int> q;
//	//不能使用vector适配,vector不支持头删
//	//yunze::queue<int, vector<int>> q;
//	
//	//队列只能使用list适配
//	//yunze::queue<int, list<int>> q;
//	q.push(1);
//	q.push(2);
//	q.push(3);
//	q.push(4);
//	while (!q.empty())
//	{
//		cout << q.front() << " ";
//		q.pop();
//	}
//	cout << endl;
//	return 0;
//}

//#include<deque>
//
//int main()
//{
//	deque<int> dp;
//	dp.push_back(1);
//	dp.push_back(1);
//	dp.push_back(1);
//	dp.push_front(2);
//	dp.push_front(3);
//	dp.push_front(4);
//	dp[0] += 10;
//	for (auto e : dp)
//	{
//		cout << e << " ";
//	}
//	cout << endl;
//	return 0;
//}

//#include<queue>
//#include<iostream>
//using namespace std;
//
////仿函数/函数对象 对象可以像函数一样使用
//template<class T>
//struct Less
//{
//	bool operator() (const T& x, const T& y) const
//	{
//		return x < y;
//	}
//};

//int main()
//{
//	//默认使用的仿函数是less,大堆
//	//priority_queue<int> pq;
//	//小堆
//	//priority_queue<int, vector<int>, greater<int>> pq;
//	//也可以使用deuqe作容器适配器,deque支持下标访问
//	priority_queue<int, deque<int>, greater<int>> pq;
//	pq.push(3);
//	pq.push(1);
//	pq.push(5);
//	pq.push(7);
//	pq.push(2);
//	while (!pq.empty())
//	{
//		cout << pq.top() << " ";
//		pq.pop();
//	}
//	cout << endl;
//
//	Less<int> less;
//	cout << less(1, 2) << endl;
//	//本质上被转换为调用operator()
//	cout << less.operator()(1, 2) << endl;
//	return 0;
//}


#include"priority_queue.h"

int main() 
{
	//yunze::priority_queue<int> pq;
	int a[] = { 30, 4, 2, 66, 3 };
	yunze::priority_queue<int> pq(a, a + 5);
	pq.push(3);
	pq.push(1);
	pq.push(5);
	pq.push(7);
	pq.push(2);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	return 0;
}

结语

相关推荐
花归去8 小时前
echarts 柱状图曲线图
开发语言·前端·javascript
智者知已应修善业8 小时前
【编写函数求表达式的值】2024-4-3
c语言·c++·经验分享·笔记·算法
田里的水稻8 小时前
C++_python_相互之间的包含调用方法
c++·chrome·python
2501_941870568 小时前
面向微服务熔断与流量削峰策略的互联网系统稳定性设计与多语言工程实践分享
开发语言·python
modelmd8 小时前
Go 编程语言指南 练习题目分享
开发语言·学习·golang
HABuo8 小时前
【Linux进程(四)】进程切换&环境变量深入剖析
linux·运维·服务器·c语言·c++·ubuntu·centos
工口发动机8 小时前
ABC440DEF简要题解
c++·算法
带土18 小时前
4. C++ static关键字
开发语言·c++
橘颂TA8 小时前
【Linux】死锁四条件的底层逻辑:从锁冲突到 STL 组件的线程安全实践(Ⅵ)
linux·运维·服务器·c++·死锁
C++ 老炮儿的技术栈8 小时前
什么是通信规约
开发语言·数据结构·c++·windows·算法·安全·链表