STL容器适配器详解:queue篇

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 理解成一个对 dequelist 的"加工封装"。

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)算法

  • 线程间通信(如消息队列)

相关推荐
草莓啵啵~41 分钟前
搜索二叉树-key的搜索模型
数据结构·c++
共享家952741 分钟前
深入理解C++ 中的list容器
c++
孞㐑¥1 小时前
C++11介绍
开发语言·c++·经验分享·笔记
云小逸1 小时前
【QQMusic项目界面开发复习笔记】第二章
c++·qt
李匠20241 小时前
C++ RPC以及cmake
网络·c++·网络协议·rpc
再睡一夏就好1 小时前
Linux常见工具如yum、vim、gcc、gdb的基本使用,以及编译过程和动静态链接的区别
linux·服务器·c语言·c++·笔记
YHY_13s2 小时前
访问者模式
c++·访问者模式
我也不曾来过12 小时前
list底层原理
数据结构·c++·list
A charmer2 小时前
C++ 日志系统实战第三步:熟悉掌握各种设计模式
c++·日志系统