1. queue
简介
在 C++ STL
中,std::queue
是一种先进先出(FIFO
)的顺序容器适配器,它只允许从队尾添加元素、从队首移除元素。这一特点使它非常适合用于任务排队、事件调度、资源请求等需要顺序处理的场景。
核心特征:
先进先出(FIFO
)
只允许队首读取,队尾插入
底层默认使用 deque
实现
是一种容器适配器(container adapter)
与stack的对比
特性 | stack(栈) | queue(队列) |
---|---|---|
数据结构 | 后进先出(LIFO) | 先进先出(FIFO) |
插入位置 | 栈顶 | 队尾 |
访问位置 | 栈顶 | 队首 |
默认底层容器 | deque | deque(也可为 list) |
应用场景 | 表达式求值、函数调用栈 | 排队系统、任务调度 |
2. 构造与常用成员函数
std::queue
是一个容器适配器,因此它必须依附于某个底层容器(默认为 std::deque
)。我们可以通过默认构造、带容器的构造等方式进行初始化。
常用构造方式
cpp
#include <queue>
#include <deque>
#include <list>
std::queue<int> q1; // 默认使用 deque<int>
std::deque<int> dq = {1, 2, 3};
std::queue<int> q2(dq); // 用已有 deque 构造
std::list<int> lst = {4, 5, 6};
std::queue<int, std::list<int>> q3(lst); // 使用 list 作为底层容器
常用成员函数
数名 | 说明 |
---|---|
push() | 向队尾添加元素 |
emplace() | 原地构造元素(效率更高) |
pop() | 移除队首元素 |
front() | 获取队首元素(不移除) |
back() | 获取队尾元素(不移除) |
empty() | 判断队列是否为空 |
size() | 返回元素个数 |
示例代码
cpp
#include <queue>
#include <iostream>
using namespace std;
int main() {
queue<int> q;
q.push(10);
q.push(20);
q.push(30);
q.emplace(40);
cout << "队首: " << q.front() << endl; // 10
cout << "队尾: " << q.back() << endl; // 30
cout << "元素个数:" << q.size() << endl; // 4
q.pop(); // 移除 10
cout << "现在的队首: " << q.front() << endl; // 20
cout << "大小: " << q.size() << endl; // 2
cout << "队列是否为空:"; // 否
if (q.empty())cout << "是" << endl;
else cout << "否" << endl;
}
3. 底层实现机制
3.1 queue
是适配器模式的典型实现
std::queue
本身并不是真正存储元素的容器,而是一个 容器适配器(Container Adapter)。它将底层容器(默认是 deque
)"包装"起来,对外暴露出 队列接口(FIFO
),并隐藏底层容器的复杂性。
可以将 queue
理解成一个对 deque
或 list
的"加工封装"。
3.2 适配器结构
std::queue<T>
(适配器类)
底层容器 (如 deque
)
操作方法:
-
push(val)
→ 调用底层容器的push_back(val)
-
pop()
→ 调用底层容器的pop_front()
-
front()
→ 调用底层容器的front()
-
back()
→ 调用底层容器的back()
-
empty()
→ 调用底层容器的empty()
-
size()
→ 调用底层容器的size()
接口适配 :queue
利用已有容器的接口,构造出自己特定语义的结构(先进先出)。这就是典型的适配器模式(Adapter Pattern) 应用。
3.3 为什么不用vector
作为底层容器?
虽然 vector
是一个强大并且连续内存的容器,但它并 不适合用于 queue
的底层实现,主要原因如下:
问题 | 说明 |
---|---|
pop() 效率低 | vector 只能从尾部删除,pop_front() 实现起来复杂,代价大,需要搬移所有元素 |
无法高效从头插入/删除 | queue 需要频繁的头删操作,而 vector 从头删是 O(n),性能差 |
deque 更合适 | deque 支持头尾高效插入/删除,恰好满足 queue 的 FIFO 特性 |
总结:
vector
适合只在尾部进行插入删除操作(比如 stack
);
queue
需要头删,尾插,最适合的底层容器是 deque
。
4. 实际应用场景
场景1:任务排队处理
在后端服务或并发程序中,常见的模型是任务按照先来先处理的顺序进行调度。std::queue
非常适合这种场景。
cpp
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include<string>
using namespace std;
// 存放任务的队列
queue<string> tasks;
// 互斥锁,保护对任务队列的并发访问
mutex mtx;
// 条件变量,用于线程间通信
condition_variable cv;
// 标记是否任务全部生成完毕
bool done = false;
//生产者线程函数:赋值往队列中添加任务
void producer() {
for (int i = 0; i < 5; ++i) {
{
unique_lock<mutex> lock(mtx); //加锁保护队列
tasks.push("任务 #" + to_string(i)); //添加任务
cout << "添加:任务 #" << i << endl;
}
cv.notify_one(); //通知等待中的消费者线程,有新任务
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟任务时间间隔
}
{
lock_guard<mutex> lock(mtx); //最后通知任务结束
done = true; //设置完成标志
cv.notify_one(); //唤醒消费者,避免永久等待
}
}
//消费者线程函数:从队列中取出任务并处理
void consumer() {
while (true) {
unique_lock<mutex> lock(mtx);
//等待条件变量:队列不空或任务已完成
cv.wait(lock, [] {return !tasks.empty() || done; });
while (!tasks.empty()) {
cout << "处理:" << tasks.front() << endl;
tasks.pop(); //处理完一个任务后移除
}
//如果done为true且队列为空,说明可以退出循环
if (done)break;
}
}
int main() {
thread t1(producer); //启动生产者线程
thread t2(consumer); //启动消费者线程
t1.join(); //等待生产者结束
t2.join(); //等待消费者结束
cout << "所有任务处理完成。" << endl;
}
输出内容:

场景2:图算法中的宽度优先搜索(BFS
)
BFS
典型地使用queue
来维护待访问的节点。
cpp
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//宽度优先搜索函数
void bfs(int start, const vector<vector<int>>& graph) {
vector<bool> visited(graph.size(), false);// 记录每个节点是否已访问
queue<int> q; // 队列用于保存待访问的节点(FIFO)
q.push(start); //将起始节点入队
visited[start] = true; //标记起始节点为已访问
while (!q.empty()) {
int cur = q.front(); //取出队首元素
q.pop(); //将其出队
cout << cur << " "; //访问该节点
//遍历当前节点的所有邻居
for (int neighbor : graph[cur]) {
if (!visited[neighbor]) { //如果该邻居未访问
visited[neighbor] = true; //标记为已访问
q.push(neighbor); //入队等待后续访问
}
}
}
}
int main() {
//图(每个数字代表一个点)
// 0
// / \
// 1 2
// \ /
// 3
vector<vector<int>> graph = {
{1,2}, //节点 0 的邻居
{0,3}, //节点 1 的邻居
{0,3}, //节点 2 的邻居
{1,2} //节点 3 的邻居
};
cout << "BFS遍历结果:";
bfs(0, graph); //从节点 0 开始 BFS,输出: 0,1,2,3
}
输出结果:

5. 自定义线程安全队列
在多线程环境下,标准库中的 std::queue
并不是线程安全的。在生产者-消费者模型中,如果多个线程同时读写同一个队列,就可能导致数据竞争和未定义行为。因此,我们需要一个线程安全的队列封装类,以保证多线程操作的正确性和安全性。
cpp
#include<iostream>
#include<queue>
#include<mutex>
#include<condition_variable>
#include<thread>
#include<string>
template<typename T>
class ThreadSafeQueue {
private:
std::queue<T> q;
mutable std::mutex mtx;
std::condition_variable cv;
public:
ThreadSafeQueue() = default;
ThreadSafeQueue(const ThreadSafeQueue&) = delete;
ThreadSafeQueue& operator=(const ThreadSafeQueue&) = delete;
//入队操作
void push(const T& value) {
std::lock_guard<std::mutex> lock(mtx);
q.push(value);
cv.notify_one(); //通知一个等待的线程
}
//出队操作(阻塞等待)
T wait_and_pop() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] {return !q.empty(); });
T value = q.front();
q.pop();
return value;
}
//出队操作(非阻塞)
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mtx);
if (q.empty())return false;
value = q.front();
q.pop();
return true;
}
bool empty() const {
std::lock_guard<std::mutex> lock(mtx);
return q.empty();
}
size_t size() const {
std::lock_guard<std::mutex> lock(mtx);
return q.size();
}
};
//使用示例(生产者-消费者模型)
ThreadSafeQueue<std::string> msg_queue;
bool done = false;
void producer() {
for (int i = 0; i < 5; ++i) {
msg_queue.push("任务" + std::to_string(i));
}
done = true;
}
void consumer() {
while (!done || !msg_queue.empty()) {
std::string msg;
if (msg_queue.try_pop(msg))
std::cout << "处理:" << msg << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
}
输出内容:

6. 总结
-
queue
是先进先出 (FIFO
)的数据结构,常用于排队模型。 -
基于 适配器模式 实现,默认底层为
deque
容器。 -
不支持随机访问,仅支持
push()
、pop()
、front()
、empty()
等接口。 -
不适合频繁中间插入或删除的场景,也不适合需要遍历全部元素的需求。
典型应用场景包括:
-
任务调度与排队系统
-
广度优先搜索(
BFS
)算法 -
线程间通信(如消息队列)