news 2026/11/3 15:27:19

C++ STL中栈与队列的核心特性与应用实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ STL中栈与队列的核心特性与应用实践

1. 理解Stack与Queue的核心特性

在C++标准模板库(STL)中,stack和queue是两种最基础的容器适配器。它们通过限制底层容器的访问方式,强制实现了特定的数据访问规则。

**stack(栈)**遵循LIFO(Last In First Out,后进先出)原则,就像餐厅里叠放的餐盘:

  • 你只能从最顶部取放餐盘
  • 最后放上去的餐盘会被最先取走
  • 典型应用:函数调用栈、表达式求值、撤销操作
#include <stack> std::stack<int> s; // 默认基于deque实现 s.push(1); // 栈顶:1 s.push(2); // 栈顶:2 → 1 s.pop(); // 移除2,栈顶变回1

**queue(队列)**遵循FIFO(First In First Out,先进先出)原则,如同超市收银台前的队伍:

  • 新顾客从队尾加入
  • 只有队首的顾客能结账离开
  • 典型应用:任务调度、消息队列、广度优先搜索
#include <queue> std::queue<int> q; q.push(1); // 队尾:1 q.push(2); // 队尾:2 ← 1 q.pop(); // 移除1,队首变为2

关键区别:stack只允许操作一端(栈顶),而queue允许操作两端(队头出、队尾入)

2. Stack的完整操作指南

2.1 底层容器选择

stack默认使用deque作为底层容器,但也可以指定其他容器:

stack<int, vector<int>> s1; // 基于vector stack<int, list<int>> s2; // 基于list

选择建议:

  • deque(默认):头尾操作都是O(1),内存占用均衡
  • vector:连续内存,但扩容时需要整体拷贝
  • list:无需扩容,但内存不连续且额外指针开销

2.2 核心接口详解

操作时间复杂度说明
push(x)O(1)将x压入栈顶,等同于emplace(x)
pop()O(1)移除栈顶元素(不返回该元素!必须先调用top()获取)
top()O(1)返回栈顶元素的引用(可修改栈顶值:s.top() = 10;)
size()O(1)返回当前元素数量(返回类型为size_t,注意与有符号整数的比较问题)
empty()O(1)判断是否为空(比size() == 0更直观)
emplace()O(1)直接构造元素,避免拷贝:s.emplace(1, "abc")而非push(MyClass(1, "abc"))

2.3 典型错误防范

  1. 空栈访问:调用top()或pop()前必须检查empty()
// 错误示范 std::stack<int> s; s.pop(); // 未定义行为! // 正确做法 if (!s.empty()) { s.pop(); }
  1. 误用pop()返回值:C++中pop()返回void,需先top()再pop()
// 错误示范 int val = s.pop(); // 编译错误! // 正确做法 int val = s.top(); s.pop();

3. Queue的深度使用解析

3.1 容器选择策略

queue默认使用deque,也可基于list实现:

queue<int, list<int>> q; // 推荐使用list

为什么不建议用vector?

  • vector的pop_front()需要移动所有元素,时间复杂度O(n)
  • deque和list的pop_front()都是O(1)

3.2 关键接口对比

操作stack版本queue版本差异说明
插入push(x)push(x)stack在栈顶,queue在队尾
删除pop()pop()stack删栈顶,queue删队头
访问top()front()queue额外提供back()访问队尾
构造插入emplace()emplace()两者用法相同

3.3 线程安全注意事项

标准库的queue不是线程安全的!多线程环境下需要额外同步:

#include <mutex> std::queue<int> msg_queue; std::mutex mtx; // 生产者线程 void producer() { std::lock_guard<std::mutex> lock(mtx); msg_queue.push(42); } // 消费者线程 void consumer() { std::lock_guard<std::mutex> lock(mtx); if (!msg_queue.empty()) { int msg = msg_queue.front(); msg_queue.pop(); } }

4. 经典算法实战

4.1 最小栈(LeetCode 155)

设计一个支持push、pop、top操作,并能常数时间检索最小元素的栈。

解决方案:使用辅助栈同步记录最小值

class MinStack { stack<int> data_stack; stack<int> min_stack; // 栈顶始终为当前最小值 public: void push(int val) { data_stack.push(val); if (min_stack.empty() || val <= min_stack.top()) { min_stack.push(val); } } void pop() { if (data_stack.top() == min_stack.top()) { min_stack.pop(); } data_stack.pop(); } int top() { return data_stack.top(); } int getMin() { return min_stack.top(); } };

4.2 用队列实现栈(LeetCode 225)

仅使用队列的基本操作实现栈。

技巧:在每次push后反转队列顺序

class MyStack { queue<int> q; public: void push(int x) { q.push(x); // 将前n-1个元素移到队尾 for (int i = 1; i < q.size(); ++i) { q.push(q.front()); q.pop(); } } int pop() { int val = q.front(); q.pop(); return val; } int top() { return q.front(); } bool empty() { return q.empty(); } };

4.3 二叉树的层序遍历(LeetCode 102)

使用队列实现BFS:

vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> result; if (!root) return result; queue<TreeNode*> q; q.push(root); while (!q.empty()) { int level_size = q.size(); vector<int> current_level; for (int i = 0; i < level_size; ++i) { TreeNode* node = q.front(); q.pop(); current_level.push_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } result.push_back(current_level); } return result; }

5. 性能优化与底层原理

5.1 内存分配策略

  • deque:分段连续存储,由多个固定大小的数组块组成

    • 优点:扩容时只需新增块,无需移动现有数据
    • 缺点:迭代器比vector复杂,内存局部性稍差
  • list:双向链表结构

    • 优点:无需连续内存,插入删除都是O(1)
    • 缺点:额外指针开销,缓存不友好

5.2 时间复杂度对比

操作vector实现deque实现list实现
pushO(1)均摊O(1)O(1)
popO(1)O(1)O(1)
随机访问O(1)O(1)O(n)

5.3 自定义内存分配器

对于高频操作场景,可自定义分配器提升性能:

#include <memory_resource> std::pmr::monotonic_buffer_resource pool; std::pmr::polymorphic_allocator<int> alloc(&pool); // 使用自定义分配器的栈 std::stack<int, std::pmr::deque<int>> s(alloc);

6. 工业级应用案例

6.1 线程池任务调度

class ThreadPool { queue<function<void()>> tasks; mutex queue_mutex; condition_variable cv; vector<thread> workers; public: ThreadPool(size_t threads) { for (size_t i = 0; i < threads; ++i) { workers.emplace_back([this] { while (true) { function<void()> task; { unique_lock<mutex> lock(queue_mutex); cv.wait(lock, [this] { return !tasks.empty(); }); task = move(tasks.front()); tasks.pop(); } task(); } }); } } void enqueue(function<void()> task) { { unique_lock<mutex> lock(queue_mutex); tasks.push(move(task)); } cv.notify_one(); } };

6.2 游戏中的事件系统

struct GameEvent { enum Type { COLLISION, UI_EVENT, ACHIEVEMENT }; Type type; time_t timestamp; // 事件数据... }; class EventSystem { queue<GameEvent> events; priority_queue<GameEvent> delayed_events; public: void pushEvent(GameEvent&& event) { if (event.timestamp <= current_time()) { events.push(move(event)); } else { delayed_events.push(move(event)); } } void processEvents() { while (!events.empty()) { dispatchEvent(events.front()); events.pop(); } while (!delayed_events.empty() && delayed_events.top().timestamp <= current_time()) { events.push(delayed_events.top()); delayed_events.pop(); } } };

7. 进阶技巧与陷阱规避

7.1 迭代器失效问题

stack和queue本身不提供迭代器,但底层容器可能产生失效:

std::stack<int, std::vector<int>> s; s.push(1); auto* vec = &s.c; // 获取底层vector的引用 vec->insert(vec->begin(), 0); // 破坏栈结构!

7.2 异常安全保证

STL操作提供以下异常保证:

  • push/emplace:强异常保证(操作失败则完全回滚)
  • pop:不抛出异常(C++标准强制要求)
  • top/front/back:无异常(仅返回引用)

7.3 自定义比较函数(优先队列)

虽然不属于标准queue,但priority_queue常与queue配合使用:

auto cmp = [](const Task& a, const Task& b) { return a.priority < b.priority; }; priority_queue<Task, vector<Task>, decltype(cmp)> pq(cmp);

8. 现代C++的最佳实践

8.1 使用emplace避免拷贝

struct Point { int x, y; }; std::stack<Point> s; s.push({1, 2}); // 构造临时对象+拷贝 s.emplace(1, 2); // 直接在栈上构造

8.2 移动语义优化

std::string prepare_data(); std::stack<std::string> stack; // 低效做法 std::string data = prepare_data(); stack.push(data); // 拷贝 // 高效做法 stack.push(prepare_data()); // 移动构造

8.3 C++17的节点操作

虽然stack/queue不直接支持,但底层容器可进行节点操作:

std::stack<int, std::list<int>> s1, s2; s1.push(42); // 转移元素所有权(无拷贝) auto node = s1.c.extract(s1.top()); // 获取底层list的节点 s2.push(std::move(node.value()));
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 15:26:49

Verilog层次化设计:子模块例化与顶层搭建实践

1. 先想明白&#xff1a;为什么非要把设计拆成子模块和顶层1.1 例化这件事&#xff0c;本质上是在画一张可复用的积木图纸刚接触硬件描述语言那阵子&#xff0c;我最常干的事就是把所有逻辑一股脑塞进一个文件里&#xff0c;一个模块几百行&#xff0c;从输入输出到状态机全在一…

作者头像 李华
网站建设 2026/11/3 15:26:41

DeepSeek本地部署排障:CUDA不可用、OOM与4-bit量化编译

简介&#xff1a;面向希望在本地私有化运行 DeepSeek 的开发者与企业运维团队&#xff0c;这份 PDF 围绕硬件选型、系统环境搭建与依赖配置给出可落地的部署参考&#xff0c;覆盖个人开发测试到企业生产集群的不同规模场景。内容按模型参数量分级列出处理器、显卡显存、内存、存…

作者头像 李华
网站建设 2026/11/3 15:26:39

PostIn开源接口管理工具:从设计到调试的全流程实践

1. PostIn工具定位与核心价值PostIn作为一款开源接口管理工具&#xff0c;其核心定位是解决开发团队在API协作中的三大痛点&#xff1a;接口频繁变更导致的文档滞后、多环境调试的配置繁琐、前后端协作的强依赖问题。与Postman这类纯调试工具不同&#xff0c;PostIn将接口设计、…

作者头像 李华
网站建设 2026/11/3 15:26:38

一颗回形针的万能改造:从办公室到电子极客的隐藏玩法

办公桌上最容易被忽略的东西&#xff0c;我提名回形针。订书机没钉了你会马上补&#xff0c;便签纸用到最后一叠你会下单&#xff0c;但回形针——只要盒里还剩三五颗&#xff0c;你根本想不起它的存在。直到哪一天手机卡托弹不出来、耳机线缠成死结、给裸主板点个亮找不到开机…

作者头像 李华
网站建设 2026/11/3 15:26:09

回形针设计、材质与应急妙用:从专利到生活救场的百年小工具

1. 一枚回形针为什么能活过一百年&#xff1a;设计、专利与材料的三重逻辑1.1 回形针的身世与设计起点回形针这个物件&#xff0c;听起来真的谈不上性感。它就是一个弯成两个环的铁丝&#xff0c;最多再镀一层亮闪闪的金属膜&#xff0c;日常躺在笔筒里、抽屉缝里和办公桌的角落…

作者头像 李华
网站建设 2026/11/3 15:24:45

非对称性在系统设计与认知框架中的核心价值

1. 非对称性作为认知基石的哲学思考 在传统认知体系中&#xff0c;对称性往往被视为完美与和谐的象征。古希腊建筑中的黄金分割、东方哲学里的阴阳平衡&#xff0c;无不体现着人类对对称美学的追求。然而当我们深入观察自然界和人类社会&#xff0c;会发现非对称性才是更普遍的…

作者头像 李华