C++ STL栈与队列详解:LIFO/FIFO原理、应用场景与性能优化
2026/9/6 1:21:00 网站建设 项目流程

这次我们来深入讲解 C++ STL 中的栈(stack)和队列(queue)容器。作为 C++ 标准模板库中最基础且实用的两种数据结构,栈和队列在算法实现、系统开发、游戏逻辑等场景中无处不在。掌握它们不仅能提升编程效率,更是面试和实际项目中的必备技能。

栈和队列的核心区别在于数据访问规则:栈遵循后进先出(LIFO),而队列遵循先进先出(FIFO)。STL 通过适配器模式实现了这两个容器,底层默认基于 deque(双端队列),也支持 list 或 vector 作为底层容器。本文将重点介绍它们的基本操作、使用场景、性能特点和实际代码示例。

1. 核心能力速览

能力项栈(stack)队列(queue)
数据结构特性后进先出(LIFO)先进先出(FIFO)
头文件#include <stack>#include <queue>
底层默认容器dequedeque
主要操作push, pop, top, empty, sizepush, pop, front, back, empty, size
时间复杂度所有操作 O(1)所有操作 O(1)
适用场景函数调用栈、表达式求值、撤销操作消息队列、任务调度、广度优先搜索

2. 栈(stack)深度解析

栈是一种限制性线性表,只允许在表的一端进行插入和删除操作,这一端称为栈顶。STL stack 提供了一套简洁的接口来管理这种 LIFO 结构。

2.1 栈的基本操作

#include <iostream> #include <stack> using namespace std; int main() { stack<int> s; // 入栈操作 s.push(10); s.push(20); s.push(30); cout << "栈大小: " << s.size() << endl; // 输出: 3 cout << "栈顶元素: " << s.top() << endl; // 输出: 30 // 出栈操作 s.pop(); cout << "弹出后栈顶: " << s.top() << endl; // 输出: 20 // 检查栈是否为空 while (!s.empty()) { cout << s.top() << " "; s.pop(); } // 输出: 20 10 return 0; }

2.2 栈的典型应用场景

表达式求值是栈的经典应用。以下是一个简单的括号匹配检查器:

#include <stack> #include <string> #include <iostream> using namespace std; bool isValidParentheses(const string& expr) { stack<char> s; for (char c : expr) { if (c == '(' || c == '[' || c == '{') { s.push(c); } else { if (s.empty()) return false; char top = s.top(); if ((c == ')' && top == '(') || (c == ']' && top == '[') || (c == '}' && top == '{')) { s.pop(); } else { return false; } } } return s.empty(); } int main() { string test1 = "({[]})"; // 有效 string test2 = "({[}])"; // 无效 cout << test1 << ": " << isValidParentheses(test1) << endl; // 输出: 1 cout << test2 << ": " << isValidParentheses(test2) << endl; // 输出: 0 return 0; }

浏览器前进后退功能也是栈的典型应用。用户访问的页面被压入栈中,后退时从栈中弹出:

class BrowserHistory { private: stack<string> backStack; stack<string> forwardStack; string current; public: BrowserHistory(string homepage) : current(homepage) {} void visit(string url) { backStack.push(current); current = url; // 清空前向栈(新访问时前向历史失效) while (!forwardStack.empty()) forwardStack.pop(); } string back(int steps) { while (steps-- > 0 && !backStack.empty()) { forwardStack.push(current); current = backStack.top(); backStack.pop(); } return current; } string forward(int steps) { while (steps-- > 0 && !forwardStack.empty()) { backStack.push(current); current = forwardStack.top(); forwardStack.pop(); } return current; } };

3. 队列(queue)全面掌握

队列是一种先进先出的线性表,只允许在表的前端进行删除操作,在表的后端进行插入操作。STL queue 完美实现了这种 FIFO 特性。

3.1 队列的基本操作

#include <iostream> #include <queue> using namespace std; int main() { queue<int> q; // 入队操作 q.push(10); q.push(20); q.push(30); cout << "队列大小: " << q.size() << endl; // 输出: 3 cout << "队首元素: " << q.front() << endl; // 输出: 10 cout << "队尾元素: " << q.back() << endl; // 输出: 30 // 出队操作 q.pop(); cout << "出队后队首: " << q.front() << endl; // 输出: 20 // 遍历队列(注意:队列不支持迭代器,需要边出队边访问) while (!q.empty()) { cout << q.front() << " "; q.pop(); } // 输出: 20 30 return 0; }

3.2 队列的典型应用场景

消息队列是队列在系统开发中的重要应用。以下是一个简单的任务调度器示例:

#include <queue> #include <string> #include <iostream> #include <thread> #include <chrono> using namespace std; class TaskScheduler { private: queue<string> taskQueue; public: void addTask(const string& task) { taskQueue.push(task); cout << "添加任务: " << task << endl; } void processTasks() { while (!taskQueue.empty()) { string task = taskQueue.front(); taskQueue.pop(); cout << "处理任务: " << task << endl; // 模拟任务处理时间 this_thread::sleep_for(chrono::seconds(1)); } cout << "所有任务处理完成!" << endl; } }; int main() { TaskScheduler scheduler; scheduler.addTask("备份数据库"); scheduler.addTask("发送邮件通知"); scheduler.addTask("生成报表"); scheduler.processTasks(); return 0; }

广度优先搜索(BFS)是队列在算法中的核心应用。以下是一个简单的图遍历示例:

#include <queue> #include <vector> #include <iostream> using namespace std; void BFS(vector<vector<int>>& graph, int start) { vector<bool> visited(graph.size(), false); queue<int> q; visited[start] = true; q.push(start); cout << "BFS遍历顺序: "; while (!q.empty()) { int node = q.front(); q.pop(); cout << node << " "; for (int neighbor : graph[node]) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } cout << endl; } int main() { // 图的邻接表表示 vector<vector<int>> graph = { {1, 2}, // 节点0的邻居 {0, 3, 4}, // 节点1的邻居 {0, 5}, // 节点2的邻居 {1}, // 节点3的邻居 {1}, // 节点4的邻居 {2} // 节点5的邻居 }; BFS(graph, 0); // 从节点0开始BFS return 0; }

4. 底层容器选择与性能优化

STL 栈和队列是容器适配器,这意味着它们基于其他序列容器实现。理解底层容器的选择对性能优化至关重要。

4.1 自定义底层容器

#include <stack> #include <queue> #include <vector> #include <list> #include <iostream> using namespace std; int main() { // 基于vector的栈 stack<int, vector<int>> s_vec; s_vec.push(1); s_vec.push(2); // 基于list的队列 queue<int, list<int>> q_list; q_list.push(10); q_list.push(20); cout << "vector栈顶: " << s_vec.top() << endl; // 输出: 2 cout << "list队列首: " << q_list.front() << endl; // 输出: 10 return 0; }

4.2 不同底层容器的性能对比

底层容器栈适用性队列适用性内存使用访问性能
deque(默认)优秀优秀中等稳定 O(1)
vector优秀不适用紧凑尾操作 O(1)
list良好优秀较高稳定 O(1)

选择建议:

  • 大多数情况下使用默认的 deque
  • 需要紧凑内存且只用于栈时选择 vector
  • 需要频繁中间插入删除时选择 list

5. 高级应用:单调栈与单调队列

单调栈和单调队列是解决特定问题的强大工具,在算法竞赛和面试中经常出现。

5.1 单调栈应用:下一个更大元素

#include <vector> #include <stack> #include <iostream> using namespace std; vector<int> nextGreaterElement(const vector<int>& nums) { int n = nums.size(); vector<int> result(n, -1); stack<int> s; // 存储索引的单调递减栈 for (int i = 0; i < n; i++) { while (!s.empty() && nums[i] > nums[s.top()]) { result[s.top()] = nums[i]; s.pop(); } s.push(i); } return result; } int main() { vector<int> nums = {2, 1, 2, 4, 3}; vector<int> result = nextGreaterElement(nums); cout << "原数组: "; for (int num : nums) cout << num << " "; cout << "\n下一个更大元素: "; for (int res : result) cout << res << " "; // 输出: 原数组: 2 1 2 4 3 // 下一个更大元素: 4 2 4 -1 -1 return 0; }

5.2 单调队列应用:滑动窗口最大值

#include <vector> #include <deque> #include <iostream> using namespace std; vector<int> maxSlidingWindow(const vector<int>& nums, int k) { if (nums.empty()) return {}; vector<int> result; deque<int> dq; // 存储索引的单调递减队列 for (int i = 0; i < nums.size(); i++) { // 移除超出窗口范围的元素 if (!dq.empty() && dq.front() == i - k) { dq.pop_front(); } // 维护单调递减性 while (!dq.empty() && nums[i] >= nums[dq.back()]) { dq.pop_back(); } dq.push_back(i); // 当窗口形成时记录最大值 if (i >= k - 1) { result.push_back(nums[dq.front()]); } } return result; } int main() { vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7}; int k = 3; vector<int> result = maxSlidingWindow(nums, k); cout << "滑动窗口最大值: "; for (int num : result) cout << num << " "; // 输出: 3 3 5 5 6 7 return 0; }

6. 常见问题与解决方案

6.1 空容器访问错误

// 错误示例 stack<int> s; s.pop(); // 未定义行为,可能崩溃 cout << s.top(); // 同样危险 // 正确做法 if (!s.empty()) { s.pop(); cout << s.top(); }

6.2 迭代器使用限制

queue<int> q; q.push(1); q.push(2); // 错误:队列没有迭代器 // for (auto it = q.begin(); it != q.end(); ++it) // 正确:通过出队方式遍历 while (!q.empty()) { cout << q.front() << " "; q.pop(); }

6.3 性能优化技巧

预分配空间(对于基于vector的栈):

stack<int, vector<int>> s; // 如果知道大致大小,可以预先reserve // 但需要直接操作底层容器: s.get_container().reserve(1000);

批量操作优化

// 批量入栈比单个入栈更高效 vector<int> data = {1, 2, 3, 4, 5}; stack<int> s; for (int num : data) { s.push(num); // 避免多次函数调用开销 }

7. 实际项目中的最佳实践

7.1 线程安全考虑

STL 容器本身不是线程安全的。在多线程环境中使用栈和队列时需要额外的同步机制:

#include <mutex> #include <stack> template<typename T> class ThreadSafeStack { private: stack<T> data; mutable mutex mtx; public: void push(T value) { lock_guard<mutex> lock(mtx); data.push(std::move(value)); } bool pop(T& value) { lock_guard<mutex> lock(mtx); if (data.empty()) return false; value = std::move(data.top()); data.pop(); return true; } bool empty() const { lock_guard<mutex> lock(mtx); return data.empty(); } };

7.2 内存管理建议

对于大型数据对象,考虑使用智能指针避免拷贝开销:

#include <memory> #include <stack> class LargeObject { // 假设这是一个大数据对象 }; int main() { stack<shared_ptr<LargeObject>> objStack; objStack.push(make_shared<LargeObject>()); // 共享所有权,避免大型对象拷贝 return 0; }

7.3 错误处理策略

实现健壮的栈和队列包装器,提供更好的错误信息:

template<typename T> class SafeStack { private: stack<T> s; public: T safePop() { if (s.empty()) { throw runtime_error("尝试从空栈弹出元素"); } T value = s.top(); s.pop(); return value; } T safeTop() const { if (s.empty()) { throw runtime_error("尝试访问空栈顶部"); } return s.top(); } };

8. 性能测试与对比分析

为了帮助读者更好地理解不同实现的性能特点,我们设计一个简单的性能测试:

#include <chrono> #include <iostream> #include <stack> #include <queue> void benchmarkStack(int operations) { stack<int> s; auto start = chrono::high_resolution_clock::now(); for (int i = 0; i < operations; i++) { s.push(i); } for (int i = 0; i < operations; i++) { s.pop(); } auto end = chrono::high_resolution_clock::now(); auto duration = chrono::duration_cast<chrono::microseconds>(end - start); cout << "栈操作 " << operations << " 次耗时: " << duration.count() << " 微秒" << endl; } void benchmarkQueue(int operations) { queue<int> q; auto start = chrono::high_resolution_clock::now(); for (int i = 0; i < operations; i++) { q.push(i); } for (int i = 0; i < operations; i++) { q.pop(); } auto end = chrono::high_resolution_clock::now(); auto duration = chrono::duration_cast<chrono::microseconds>(end - start); cout << "队列操作 " << operations << " 次耗时: " << duration.count() << " 微秒" << endl; } int main() { benchmarkStack(100000); benchmarkQueue(100000); return 0; }

栈和队列作为 C++ STL 的基础组件,其设计简洁而强大。掌握它们不仅意味着能够处理 LIFO 和 FIFO 场景,更重要的是理解容器适配器的设计思想。在实际项目中,合理选择底层容器、注意线程安全、优化内存使用,都能显著提升代码质量和性能表现。

对于需要处理更复杂场景的开发者,可以进一步研究 priority_queue(优先队列)和 deque(双端队列),它们提供了更多的灵活性和功能。栈和队列的掌握程度直接影响到算法实现和系统设计的质量,建议通过实际编码练习来加深理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询