一、C++ 队列(queue)的核心性质
队列是 C++ STL 中的容器适配器 (不是原生容器),它封装了底层容器(默认是
deque),并提供符合 "先进先出(FIFO, First In First Out)" 规则的操作接口。核心性质如下:1. 底层实现
queue本身不存储数据,而是依赖其他容器作为 "底层存储",属于适配器模式。- 默认底层容器是
std::deque(双端队列),也可以手动指定为std::list(只要满足back()、front()、push_back()、pop_front()、empty()、size()接口的容器都可以)。- 选择
deque作为默认的原因:兼顾了数组的随机访问优势和链表的高效插入 / 删除优势,且避免了vector扩容时的内存拷贝开销。2. 访问 / 操作规则(核心)
- 访问限制 :只能访问队首 (第一个入队的元素)和队尾 (最后一个入队的元素),不支持随机访问 (比如通过下标
q[0]访问),也没有迭代器 (无法用for循环遍历)。- 操作限制 :只能在队尾插入 元素,只能在队首删除元素,完全符合 "排队" 的逻辑。
3. 其他特性
- 不支持排序、反转等操作(因为 FIFO 规则不允许);
- 线程不安全(多线程操作需手动加锁)。
二、queue 的常用接口(附代码示例)
包含头文件:
#include <queue>
std::queue的核心设计目标是严格遵循先进先出(FIFO) 规则,它的接口是为这个规则量身定制的:
- 允许的操作:队尾插入 (push/emplace)、队首删除(pop);
- 禁止的操作:删除队尾、删除中间元素、随机删除 ------ 这些操作会破坏 FIFO 的核心逻辑,因此标准库刻意隐藏了这些接口。
1. 构造函数
| 构造方式 | 说明 |
|---|---|
queue<T> q; |
默认构造,底层为deque<T>,空队列 |
queue<T, Container> q; |
指定底层容器(如list<T>) |
queue<T> q2(q1); |
拷贝构造,复制 q1 的所有元素到 q2 |
2. 元素访问(仅 2 个接口)
| 接口 | 说明 | 注意事项 |
|---|---|---|
q.front() |
返回队首元素的引用(可修改) | 队列空时调用会导致未定义行为 |
q.back() |
返回队尾元素的引用(可修改) | 队列空时调用会导致未定义行为 |
3. 容量判断
| 接口 | 说明 |
|---|---|
q.empty() |
判断队列是否为空,返回bool(空返回true) |
q.size() |
返回队列中元素的个数,类型为size_t |
4. 修改操作(核心)
| 接口 | 说明 | 注意事项 |
|---|---|---|
q.push(val) |
在队尾插入元素val(拷贝 / 移动构造) |
- |
q.emplace(args...) |
在队尾直接构造 元素(无需先创建对象),比push更高效 |
C++11 及以上支持 |
q.pop() |
删除队首元素,无返回值 | 队列空时调用会导致未定义行为 |
q.swap(q2) |
交换两个队列的内容(底层容器也会交换) | - |
cpp
#include <iostream>
#include <queue>
#include <list> // 用于指定底层容器
using namespace std;
// 定义一个简单的结构体,演示emplace的用法
struct Person {
string name;
int age;
// 构造函数
Person(string n, int a) : name(n), age(a) {}
};
int main() {
// ========== 1. 构造队列 ==========
// 默认构造(底层deque)
queue<int> q1;
// 指定底层容器为list
queue<int, list<int>> q2;
// ========== 2. 插入元素 ==========
q1.push(10); // 队尾插入10
q1.push(20); // 队尾插入20
q1.emplace(30); // 直接构造30并插入队尾(效果同push,但更高效)
// 对自定义类型使用emplace(直接传构造参数,无需先创建Person对象)
queue<Person> q_person;
q_person.emplace("张三", 20); // 直接构造
q_person.push(Person("李四", 25)); // 先创建对象再拷贝
// ========== 3. 访问元素 ==========
cout << "队首元素:" << q1.front() << endl; // 输出10
cout << "队尾元素:" << q1.back() << endl; // 输出30
// 修改队尾元素
q1.back() = 35;
cout << "修改后队尾元素:" << q1.back() << endl; // 输出35
// ========== 4. 容量判断 ==========
if (!q1.empty()) {
cout << "队列大小:" << q1.size() << endl; // 输出3
}
// ========== 5. 删除元素 ==========
q1.pop(); // 删除队首的10
cout << "pop后队首元素:" << q1.front() << endl; // 输出20
// ========== 6. 交换队列 ==========
queue<int> q3;
q3.push(100);
q1.swap(q3); // 交换q1和q3的内容
cout << "交换后q1的队首:" << q1.front() << endl; // 输出100
cout << "交换后q3的队首:" << q3.front() << endl; // 输出20
return 0;
}
编译运行说明:
-
编译命令(g++):
g++ -std=c++11 queue_demo.cpp -o queue_demo(-std=c++11用于支持emplace); -
运行:
./queue_demo; -
输出结果:
队首元素:10 队尾元素:30 修改后队尾元素:35 队列大小:3 pop后队首元素:20 交换后q1的队首:100 交换后q3的队首:20
三、常见易错点提醒
- pop () 无返回值 :很多新手误以为
pop()会返回被删除的队首元素,实际需要先调用front()获取值,再调用pop()删除; - 空队列操作 :调用
front()、back()、pop()前,必须用empty()检查队列非空,否则会导致程序崩溃(未定义行为); - 迭代器缺失 :
queue没有迭代器,无法用for (auto& e : q)遍历,若需要遍历,可先逐个front()+pop()取出元素(会清空队列),或改用deque/list。
四、队列的底层实现
queue本身不直接实现数据存储和基础操作,它是一种 "适配器(Adapter)"------ 本质是对现有容器的接口进行 "包装和限制",只暴露符合 FIFO(先进先出)规则的操作,隐藏底层容器的其他功能。可以用通俗的比喻理解:
- 底层容器(如 deque/list)是 "多功能工具箱",有各种工具(随机访问、首尾操作、中间插入等);
queue适配器是 "定制化外壳",只留两个开口:"从尾部放东西""从头部取东西",把其他工具都盖住,只保留符合排队逻辑的功能。
默认底层容器:std::deque(双端队列)
1. 为什么选择 deque 作为默认?
C++ 标准委员会选择
deque而非vector/list,是综合性能、内存效率的最优解:
容器 优点 缺点 适配 queue 的问题 vector随机访问快、内存连续 队首删除(pop_front)效率极低(需移动所有元素);扩容时内存拷贝 不符合 queue "高效删除队首" 的需求 list首尾插入 / 删除效率 O (1) 无随机访问、内存碎片化、额外指针开销 性能尚可,但内存效率不如 deque deque首尾插入 / 删除 O (1);内存分段连续(无整体扩容拷贝);支持随机访问 中间插入 / 删除效率低(但 queue 用不到) 完美匹配 queue 的操作需求
2. deque 的底层结构(简化版)
deque不是单一连续数组,而是由:
- 中控器:一个存储 "分段数组指针" 的连续数组;
- 分段缓冲区:多个固定大小的连续数组(称为 "缓冲区")。
这种结构让
deque既能像list一样高效地在首尾插入 / 删除(只需操作缓冲区的首尾,无需移动大量元素),又能像vector一样接近连续的内存访问(减少缓存失效)。
3. queue 对 deque 的接口映射
queue的所有操作,本质都是调用底层deque的对应接口,仅做了 "功能筛选":
queue 接口 底层 deque 的接口 说明 push(val)push_back(val)队尾插入 → deque 尾部插入 emplace()emplace_back()队尾构造 → deque 尾部构造 pop()pop_front()队首删除 → deque 头部删除 front()front()访问队首 → 访问 deque 头部 back()back()访问队尾 → 访问 deque 尾部 empty()empty()判断空 → 复用 deque 判断 size()size()获取大小 → 复用 deque 大小
自定义底层容器(以 list 为例)
你可以手动指定
queue的底层容器,只要该容器满足以下最小接口要求:
- 支持
empty()、size();- 支持
front()、back();- 支持
push_back()、pop_front()。
示例:用 list 作为 queue 的底层容器
cpp
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main() {
// 显式指定底层容器为std::list<int>
queue<int, list<int>> q;
// 操作逻辑和默认queue完全一致,底层实际调用list的接口
q.push(10);
q.push(20);
q.push(30);
cout << "队首:" << q.front() << endl; // 10,调用list::front()
cout << "队尾:" << q.back() << endl; // 30,调用list::back()
q.pop(); // 调用list::pop_front(),删除队首
cout << "pop后队首:" << q.front() << endl; // 20
cout << "队列大小:" << q.size() << endl; // 2,调用list::size()
return 0;
}
运行结果:
队首:10
队尾:30
pop后队首:20
队列大小:2
用deque模拟实现简易版 queue 适配器
cpp
#include <iostream>
#include <deque>
using namespace std;
// 简易版queue适配器
template <typename T, typename Container = deque<T>>
class MyQueue {
private:
Container c; // 底层容器,默认是deque
public:
// 判空:直接调用底层容器的empty()
bool empty() const { return c.empty(); }
// 大小:直接调用底层容器的size()
size_t size() const { return c.size(); }
// 访问队首:调用底层容器的front()
T& front() { return c.front(); }
const T& front() const { return c.front(); }
// 访问队尾:调用底层容器的back()
T& back() { return c.back(); }
const T& back() const { return c.back(); }
// 队尾插入:调用底层容器的push_back()
void push(const T& val) { c.push_back(val); }
// 队首删除:调用底层容器的pop_front()
void pop() { c.pop_front(); }
};
int main() {
MyQueue<int> q;
q.push(1);
q.push(2);
cout << q.front() << endl; // 1
q.pop();
cout << q.front() << endl; // 2
return 0;
}
用list模拟实现简易版 queue 适配器
因为list本身支持高效的首尾插入 / 删除操作(均为 O (1) 时间复杂度),完全满足queue的适配要求,核心思路是:
- 定义模板类
MyQueue,底层固定使用std::list作为存储容器; - 将
queue的核心接口(empty()/size()/front()/back()/push()/pop())直接映射到list的对应接口; - 仅暴露符合 FIFO 规则的操作,隐藏
list的其他接口(如中间插入、随机访问等),体现适配器的 "封装 + 限制" 本质。
cpp
#include <iostream>
#include <list> // 作为底层容器
using namespace std;
// 以list为底层容器的简易版queue适配器
template <typename T> // 模板参数:队列存储的元素类型
class MyQueue {
private:
// 私有成员:底层存储容器(std::list)
list<T> container;
public:
// ========== 1. 容量相关 ==========
// 判断队列是否为空:调用list的empty()
bool empty() const {
return container.empty();
}
// 获取队列元素个数:调用list的size()
size_t size() const {
return container.size();
}
// ========== 2. 元素访问 ==========
// 访问队首元素:调用list的front(),返回引用(支持修改)
T& front() {
// 空队列访问队首属于未定义行为,这里简单判断并提示
if (empty()) {
throw runtime_error("MyQueue is empty, cannot access front!");
}
return container.front();
}
// 常量版本的front(只读,不能修改)
const T& front() const {
if (empty()) {
throw runtime_error("MyQueue is empty, cannot access front!");
}
return container.front();
}
// 访问队尾元素:调用list的back(),返回引用(支持修改)
T& back() {
if (empty()) {
throw runtime_error("MyQueue is empty, cannot access back!");
}
return container.back();
}
// 常量版本的back(只读)
const T& back() const {
if (empty()) {
throw runtime_error("MyQueue is empty, cannot access back!");
}
return container.back();
}
// ========== 3. 修改操作 ==========
// 队尾插入元素:调用list的push_back()
void push(const T& value) {
container.push_back(value);
}
// 队首删除元素:调用list的pop_front()(无返回值)
void pop() {
if (empty()) {
throw runtime_error("MyQueue is empty, cannot pop!");
}
container.pop_front();
}
};
// 测试简易版MyQueue
int main() {
try {
MyQueue<int> q;
// 1. 测试空队列判断
cout << "队列是否为空:" << (q.empty() ? "是" : "否") << endl; // 输出:是
// 2. 插入元素(队尾)
q.push(10);
q.push(20);
q.push(30);
cout << "插入3个元素后,队列大小:" << q.size() << endl; // 输出:3
// 3. 访问队首/队尾
cout << "队首元素:" << q.front() << endl; // 输出:10
cout << "队尾元素:" << q.back() << endl; // 输出:30
// 4. 修改队尾元素(通过引用)
q.back() = 35;
cout << "修改后队尾元素:" << q.back() << endl; // 输出:35
// 5. 删除队首元素
q.pop();
cout << "pop后队首元素:" << q.front() << endl; // 输出:20
cout << "pop后队列大小:" << q.size() << endl; // 输出:2
// 6. 继续pop直到空
q.pop();
q.pop(); // 此时队列已空,再pop会抛异常
// q.pop(); // 取消注释会触发异常:MyQueue is empty, cannot pop!
} catch (const exception& e) {
cout << "异常信息:" << e.what() << endl;
}
return 0;
}