目录
一.stack(栈)
- 栈的本质:容器适配器
首先理解关键点:C++中的stack不是独立的容器,而是"容器适配器"。它不自己管理内存,而是包装一个已有的底层容器(默认是deque),为其添加栈的接口(LIFO操作)。
- 为什么需要基于已有容器构造?
场景一:快速构建已有数据的栈视图
假设你有一个现成的数据集合,原本用vector或list存储,现在需要以"后进先出"的方式处理。这时你可以:
-
选择一:写循环,逐个push进栈 → 需要额外代码,且效率较低
-
选择二:直接用已有容器构造栈 → 简洁高效,底层直接复用已有数据
场景二:数据处理的阶段转换
程序中常有这种模式:
-
收集阶段:用vector等随机访问容器收集数据
-
处理阶段:需要按特定顺序(如逆序)处理
这时将已有容器构造为栈,就是自然的转换桥梁。
场景三:复用已有容器特性
不同底层容器有不同特性:
-
deque(默认):两端增删快,内存非连续
-
vector:内存连续,缓存友好,但只能在末端增删
-
list:任何位置增删快,但内存不连续
当你的程序已经使用了特定容器,并想基于它实现栈操作时,这个构造函数让你能保持原有容器类型的同时获得栈接口。
官网:cplusplus.com/reference/stack/stack/?kw=stack
1.1.构造函数
作为一个容器适配器,它只有一种构造函数
explicit stack(const container_type& ctnr = container_type());
这个构造函数有两种用法:
-
默认构造:创建空栈
-
使用已有容器构造:复制容器内容作为栈的初始内容
cpp
#include <iostream>
#include <stack>
#include <vector>
#include <deque>
int main() {
// 1. 默认构造 - 空栈
std::stack<int> s1; // 使用默认的deque作为底层容器
std::cout << "s1 size: " << s1.size() << std::endl; // 输出: 0
// 2. 使用vector作为底层容器(指定容器类型)
std::stack<int, std::vector<int>> s2;
std::cout << "s2 size: " << s2.size() << std::endl; // 输出: 0
// 3. 使用已有容器初始化(复制容器内容)
std::vector<int> vec = {1, 2, 3, 4, 5};
std::stack<int, std::vector<int>> s3(vec); // 使用vec初始化栈
std::cout << "s3 size: " << s3.size() << std::endl; // 输出: 5
// 查看栈顶元素(应该是vec的最后一个元素)
std::cout << "s3 top: " << s3.top() << std::endl; // 输出: 5
// 4. 使用deque初始化(stack默认容器)
std::deque<int> deq = {10, 20, 30};
std::stack<int> s4(deq); // 使用deque初始化栈
std::cout << "s4 top: " << s4.top() << std::endl; // 输出: 30
// 5. 弹出元素验证栈的顺序(LIFO)
std::cout << "\nPopping s3 elements: ";
while (!s3.empty()) {
std::cout << s3.top() << " ";
s3.pop();
}
// 输出: 5 4 3 2 1 (后进先出)
return 0;
}
- explicit 关键字的作用
cpp
// 错误示例:不能隐式转换
std::vector<int> vec = {1, 2, 3};
// std::stack<int> s = vec; // 错误!不能隐式转换
// 正确示例:必须显式调用构造函数
std::stack<int, std::vector<int>> s(vec); // 正确
2.底层容器类型
默认使用 deque<T>
可以指定其他容器,但必须满足四个基本操作就可以作为stack的底层容器:
-
push_back() - 在末尾添加元素(对应stack的push)
-
pop_back() - 从末尾删除元素(对应stack的pop)
-
back() - 访问末尾元素(对应stack的top)
-
empty() 和 size() - 容量查询
我们再看一个例子
cpp
#include <iostream>
#include <stack>
#include <list>
int main() {
// 使用list作为底层容器初始化栈
std::list<std::string> tasks = {"Task A", "Task B", "Task C"};
std::stack<std::string, std::list<std::string>> taskStack(tasks);
std::cout << "Processing tasks (LIFO):\n";
while (!taskStack.empty()) {
std::cout << "Processing: " << taskStack.top() << std::endl;
taskStack.pop();
}
// 输出:
// Processing: Task C
// Processing: Task B
// Processing: Task A
return 0;
}
1.2.常用操作
作为容器适配器,我们的stack也是提供了一些栈独有的数据结构。
empty
- 作用:检查栈是否为空
- 返回:bool 类型,栈为空时返回 true,否则返回 false
cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> myStack;
// 检查空栈
if (myStack.empty()) {
std::cout << "栈是空的" << std::endl; // 会执行这里
} else {
std::cout << "栈不是空的" << std::endl;
}
// 添加元素后检查
myStack.push(10);
if (!myStack.empty()) {
std::cout << "现在栈不是空的" << std::endl; // 会执行这里
}
// 清空栈后检查
myStack.pop();
if (myStack.empty()) {
std::cout << "栈又变成空的了" << std::endl; // 会执行这里
}
return 0;
}
size
- 作用:返回栈中当前元素的个数
- 返回:无符号整型,表示元素数量
cpp
#include <iostream>
#include <stack>
int main()
{
std::stack<std::string> nameStack;
// 初始大小
std::cout << "初始大小: " << nameStack.size() << std::endl; // 输出: 0
// 添加元素
nameStack.push("Alice");
nameStack.push("Bob");
nameStack.push("Charlie");
std::cout << "添加3个元素后的大小: " << nameStack.size() << std::endl; // 输出: 3
// 移除元素
nameStack.pop();
std::cout << "移除1个元素后的大小: " << nameStack.size() << std::endl; // 输出: 2
// 继续添加
nameStack.push("David");
nameStack.push("Eve");
std::cout << "再添加2个元素后的大小: " << nameStack.size() << std::endl; // 输出: 4
return 0;
}
top
- 作用:访问栈顶元素(下一个要处理的元素),但是不会移动栈顶元素
- 它返回栈顶元素的引用(可修改) ,但不删除该元素。
- 注意:不会移除元素,只是查看。如果栈为空调用此函数是未定义行为
cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> numbers;
// 示例1: 基本使用
numbers.push(10);
numbers.push(20);
numbers.push(30);
std::cout << "栈顶元素: " << numbers.top() << std::endl; // 输出: 30
// 示例2: 查看但不删除
std::cout << "再次查看栈顶: " << numbers.top() << std::endl; // 输出: 30
std::cout << "栈大小仍然是: " << numbers.size() << std::endl; // 输出: 3
// 示例3: 修改栈顶元素
numbers.top() = 99; // 修改栈顶元素的值
std::cout << "修改后栈顶元素: " << numbers.top() << std::endl; // 输出: 99
return 0;
}
push
- 作用:插入元素到栈顶,成为新的栈顶
- 参数:要插入的元素值(会复制或移动)
cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> numbers;
// 基本使用
std::cout << "初始栈大小: " << numbers.size() << std::endl;
numbers.push(10);
std::cout << "push(10)后,栈顶: " << numbers.top() << std::endl;
std::cout << "栈大小: " << numbers.size() << std::endl;
numbers.push(20);
std::cout << "push(20)后,栈顶: " << numbers.top() << std::endl;
std::cout << "栈大小: " << numbers.size() << std::endl;
numbers.push(30);
std::cout << "push(30)后,栈顶: " << numbers.top() << std::endl;
std::cout << "栈大小: " << numbers.size() << std::endl;
return 0;
}
emplace
- 作用:在栈顶原地构造元素
- 用法:相比 push 更高效,直接在栈顶位置构造对象,避免临时对象的创建和拷贝
- 参数:构造元素所需的参数列表
cpp
#include <iostream>
#include <stack>
#include <string>
class Person {
public:
std::string name;
int age;
// 构造函数
Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person对象被构造: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name), age(other.age) {
std::cout << "Person对象被拷贝: " << name << std::endl;
}
// 移动构造函数
Person(Person&& other) noexcept : name(std::move(other.name)), age(other.age) {
std::cout << "Person对象被移动: " << name << std::endl;
}
};
int main() {
std::stack<Person> peopleStack;
std::cout << "=== 使用 push() 创建临时对象 ===" << std::endl;
// push会创建临时对象,然后拷贝或移动到栈中
peopleStack.push(Person("Alice", 25));
// 输出:
// Person对象被构造: Alice (临时对象)
// Person对象被移动: Alice (移动到栈中)
std::cout << "\n=== 使用 emplace() 直接构造 ===" << std::endl;
// emplace直接在栈中构造对象,避免临时对象
peopleStack.emplace("Bob", 30);
// 输出:
// Person对象被构造: Bob (直接在栈中构造)
std::cout << "\n=== 访问栈顶 ===" << std::endl;
std::cout << "栈顶人员: " << peopleStack.top().name << std::endl; // Bob
// 更多emplace示例
std::stack<std::pair<int, std::string>> pairStack;
// 使用push需要创建pair对象
pairStack.push(std::pair<int, std::string>(1, "Hello"));
// 使用emplace更简洁高效
pairStack.emplace(2, "World"); // 直接在栈中构造pair
std::cout << "\n栈顶pair: " << pairStack.top().first
<< ", " << pairStack.top().second << std::endl;
return 0;
}
pop
- 作用:移除栈顶元素,弹出并丢弃当前栈顶元素
- 注意:不返回被移除的元素,只移除。需要先使用 top() 获取值
cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> numbers;
// 添加元素
for (int i = 1; i <= 5; i++) {
numbers.push(i * 10);
std::cout << "push(" << i * 10 << ")" << std::endl;
}
std::cout << "\n当前栈大小: " << numbers.size() << std::endl;
std::cout << "当前栈顶: " << numbers.top() << std::endl;
std::cout << "\n=== 开始pop操作 ===" << std::endl;
// 安全地pop元素
if (!numbers.empty()) {
std::cout << "准备pop,当前栈顶: " << numbers.top() << std::endl;
numbers.pop(); // 移除50
std::cout << "pop后栈顶变为: " << (numbers.empty() ? "空" : std::to_string(numbers.top())) << std::endl;
}
std::cout << "\n=== 循环pop所有元素 ===" << std::endl;
int count = 0;
while (!numbers.empty()) {
count++;
std::cout << "第" << count << "次pop前,栈顶: " << numbers.top() << std::endl;
numbers.pop();
std::cout << "pop后栈大小: " << numbers.size() << std::endl;
}
std::cout << "\n最终栈是否为空: " << (numbers.empty() ? "是" : "否") << std::endl;
return 0;
}
swap
- 作用:交换两个栈的内容
- 用法:高效交换两个栈的所有元素(只交换内部指针,不逐个拷贝元素)
- 参数:要交换的另一个栈
cpp
#include <iostream>
#include <stack>
#include <string>
int main() {
std::stack<std::string> stack1;
std::stack<std::string> stack2;
// 初始化stack1
stack1.push("Apple");
stack1.push("Banana");
stack1.push("Cherry");
// 初始化stack2
stack2.push("Dog");
stack2.push("Cat");
std::cout << "交换前:" << std::endl;
std::cout << "stack1 大小: " << stack1.size() << ", 栈顶: "
<< (stack1.empty() ? "空" : stack1.top()) << std::endl;
std::cout << "stack2 大小: " << stack2.size() << ", 栈顶: "
<< (stack2.empty() ? "空" : stack2.top()) << std::endl;
// 交换两个栈的内容
stack1.swap(stack2);
std::cout << "\n交换后:" << std::endl;
std::cout << "stack1 大小: " << stack1.size() << ", 栈顶: "
<< (stack1.empty() ? "空" : stack1.top()) << std::endl;
std::cout << "stack2 大小: " << stack2.size() << ", 栈顶: "
<< (stack2.empty() ? "空" : stack2.top()) << std::endl;
return 0;
}
二.queue(队列)
与栈类似,队列(queue)也是容器适配器,它提供FIFO(先进先出)的接口。这个构造函数同样有两种用法:
-
默认构造:创建一个空队列。
-
使用已有容器构造:复制容器内容作为队列的初始内容。
为什么需要基于其他容器构造队列?
- 队列的本质:容器适配器
队列和栈一样,是容器适配器。它基于一个底层容器(默认是deque)提供FIFO操作。这意味着队列并不关心底层数据是如何存储的,只关心如何按照先进先出的方式访问。
队列作为容器适配器,本质上是一个"FIFO规则包装器":
-
它不创造数据存储能力,而是复用现有容器的存储
-
它不增加新功能,而是限制功能以强化规则
-
它不改变数据顺序,而是赋予顺序特定语义
这个构造函数允许你将任意兼容的容器瞬间转化为一个排队系统,是"数据已有,现需排队处理"场景下的优雅解决方案。它体现了软件工程中"组合优于继承"、"单一职责"等重要原则,是C++标准库中一个精心设计的抽象。
- 基于已有容器构造的实际意义
场景一:快速转换已有数据为队列
假设你已经有一个容器(比如vector或list)存储了一些数据,现在你需要按照先进先出的顺序处理这些数据。使用这个构造函数,你可以直接将已有容器转换为队列,而不需要逐个元素入队。
例如:
- 你有一个存储任务的vector,现在想按照到达顺序(即vector中的顺序)处理任务,那么可以直接用vector构造队列。
场景二:复用不同底层容器
队列默认使用deque作为底层容器,但也可以指定为list或vector。不同底层容器有不同的性能特点:
-
deque(默认):两端插入删除高效,内存分段连续。
-
list:任意位置插入删除高效,但内存不连续。
-
vector:内存连续,但只能在末尾高效插入,在头部插入效率低。
如果你已经有一个list或vector,并且想用它作为队列的底层容器,这个构造函数可以让你直接使用已有容器初始化队列。
场景三:数据流水线处理
在数据处理管道中,可能前一阶段使用其他容器存储数据,后一阶段需要队列行为。使用这个构造函数可以方便地将数据转移到队列中,以便进行FIFO处理。
- 与栈的对比
栈是LIFO,队列是FIFO,但两者都是容器适配器。因此,这个构造函数的设计意图是相似的:允许用户从已有容器快速构建一个具有特定访问顺序(栈是后进先出,队列是先进先出)的数据结构。
- 注意点
-
构造函数是
explicit的,这意味着你不能隐式转换,必须显式调用构造函数。 -
使用已有容器构造时,会复制容器中的内容。这意味着原容器和队列彼此独立,修改队列不会影响原容器,反之亦然。
底层容器的要求
队列需要一个"既能从前面拿,又能从后面放"的容器:
-
必须支持 :
push_back()(队尾入队)、pop_front()(队头出队) -
最好支持 :
front()(查看队头)、back()(查看队尾)
这就是为什么默认用 deque(双端队列):
-
头尾操作都高效(O(1)时间)
-
内存动态增长,比vector更灵活
接口的精心限制
队列只暴露6个核心操作:
-
入队(后端插入)
-
出队(前端移除)
-
看队头(不拿走)
-
看队尾(不拿走)
-
判空
-
查大小
隐藏了底层容器的随机访问、中间插入删除等"破坏FIFO"的功能。
2.1.构造函数
我们创建一个队列(queue)时,可以使用这个构造函数。
cpp
explicit queue (const container_type& ctnr = container_type());
它有两种使用方式:
-
默认构造:创建一个空的队列。
-
使用一个现有的容器(该容器类型必须与队列的底层容器类型相同)来初始化队列,队列中的元素将是该容器中元素的副本,并且顺序与容器中的顺序相同(即容器的开始处为队首,结束处为队尾)。
注意:队列是一种适配器,它默认使用deque作为底层容器,但也可以使用list等满足其操作的容器。
1.为什么是 explicit?
防止意外的隐式转换,避免歧义:
cpp
std::deque<int> data = {1, 2, 3};
// 正确:显式构造
std::queue<int> q1(data);
// 错误:不能隐式转换(因为有explicit)
// std::queue<int> q2 = data; // 编译错误
默认参数的作用
cpp
std::queue<int> q1; // 空队列,使用默认构造的deque
std::queue<int> q2(data); // 用已有deque构造队列
基于容器构造队列时,会保持元素的原有顺序:
cpp
std::vector<int> vec = {1, 2, 3, 4, 5};
std::queue<int, std::vector<int>> q(vec);
// 队列顺序:1 → 2 → 3 → 4 → 5
// 1会先出队,5最后出队
性能考虑
- 构造时的复制:构造函数会复制整个容器,对于大数据集需要注意性能
- 容器类型匹配:指定的容器类型必须支持队列所需操作(front、back、push_back、pop_front)
我们现在直接看例子
cpp
#include <iostream>
#include <queue>
#include <deque>
#include <list>
int main() {
// 1. 默认构造 - 空队列
std::queue<int> q1; // 使用默认的deque作为底层容器
std::cout << "q1 size: " << q1.size() << std::endl; // 输出: 0
// 2. 使用list作为底层容器(指定容器类型)
std::queue<int, std::list<int>> q2;
std::cout << "q2 size: " << q2.size() << std::endl; // 输出: 0
// 3. 使用已有容器初始化(复制容器内容)
std::deque<int> deq = {1, 2, 3, 4, 5};
std::queue<int> q3(deq); // 使用deque初始化队列
std::cout << "q3 size: " << q3.size() << std::endl; // 输出: 5
// 查看队首元素(应该是deq的第一个元素)
std::cout << "q3 front: " << q3.front() << std::endl; // 输出: 1
// 4. 使用list初始化
std::list<int> lst = {10, 20, 30};
std::queue<int, std::list<int>> q4(lst); // 使用list初始化队列
std::cout << "q4 front: " << q4.front() << std::endl; // 输出: 10
// 5. 出队元素验证队列的顺序(FIFO)
std::cout << "\n出队q3元素: ";
while (!q3.empty()) {
std::cout << q3.front() << " ";
q3.pop();
}
// 输出: 1 2 3 4 5 (先进先出)
return 0;
}

2.2.常用操作
作为一个容器适配器,queue提供了一些队列专有的操作

empty
-
作用:检查队列是否为空
-
详细说明:判断队列中是否有元素。这是一个安全保护函数,在进行任何可能访问或移除元素的操作之前,都应该先用这个函数检查队列是否为空,避免对空队列进行操作导致程序错误
cpp
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<int> numberQueue;
std::queue<std::string> textQueue;
std::cout << "=== empty() 方法演示 ===" << std::endl;
// 示例1: 检查空队列
std::cout << "\n1. 检查空队列:" << std::endl;
if (numberQueue.empty()) {
std::cout << "数字队列是空的" << std::endl; // 会执行这里
} else {
std::cout << "数字队列不是空的" << std::endl;
}
// 示例2: 添加元素后检查
std::cout << "\n2. 添加元素后检查:" << std::endl;
numberQueue.push(100);
if (!numberQueue.empty()) {
std::cout << "现在数字队列不是空的" << std::endl; // 会执行这里
}
// 示例3: 清空队列后检查
std::cout << "\n3. 清空队列后检查:" << std::endl;
numberQueue.pop(); // 移除唯一元素
if (numberQueue.empty()) {
std::cout << "数字队列又变成空的了" << std::endl; // 会执行这里
}
// 示例4: 实际应用中的安全检查
std::cout << "\n4. 队列操作前的安全检查:" << std::endl;
// 错误做法:直接访问空队列
// std::cout << "队首元素: " << numberQueue.front() << std::endl; // 可能导致崩溃
// 正确做法:先检查是否为空
if (!textQueue.empty()) {
std::cout << "文本队首: " << textQueue.front() << std::endl;
} else {
std::cout << "文本队列为空,无法访问队首" << std::endl; // 会执行这里
}
// 添加一些数据
textQueue.push("第一条消息");
textQueue.push("第二条消息");
// 现在可以安全访问
if (!textQueue.empty()) {
std::cout << "添加数据后,队首: " << textQueue.front() << std::endl;
}
return 0;
}

size
-
作用:返回队列中当前元素的个数
-
详细说明:返回队列包含的元素数量。这个值是一个非负整数,表示队列的长度。随着元素的入队和出队,这个值会动态变化
cpp
#include <iostream>
#include <queue>
int main() {
std::queue<int> queue;
std::cout << "=== size() 方法演示 ===" << std::endl;
// 示例1: 初始大小
std::cout << "\n1. 初始队列大小:" << std::endl;
std::cout << "队列初始大小: " << queue.size() << std::endl; // 输出: 0
// 示例2: 添加元素
std::cout << "\n2. 添加元素:" << std::endl;
queue.push(10);
std::cout << "push(10)后大小: " << queue.size() << std::endl; // 输出: 1
queue.push(20);
queue.push(30);
std::cout << "再push(20), push(30)后大小: " << queue.size() << std::endl; // 输出: 3
// 示例3: 移除元素
std::cout << "\n3. 移除元素:" << std::endl;
queue.pop();
std::cout << "pop()后大小: " << queue.size() << std::endl; // 输出: 2
// 示例4: 批量操作
std::cout << "\n4. 批量操作:" << std::endl;
for (int i = 4; i <= 7; i++) {
queue.push(i * 10);
std::cout << "push(" << i * 10 << ")后大小: " << queue.size() << std::endl;
}
// 示例5: 清空队列
std::cout << "\n5. 清空队列:" << std::endl;
while (!queue.empty()) {
queue.pop();
std::cout << "pop()后大小: " << queue.size() << std::endl;
}
std::cout << "\n最终队列大小: " << queue.size() << std::endl;
return 0;
}
front
-
作用:访问队头元素(下一个要处理的元素),返回这个元素的引用
-
详细说明 :返回队列中最先进入的那个元素,也就是下一个将要被处理的元素。这个操作只查看不删除元素。如果队列为空时调用这个函数,结果是未定义的,可能导致程序崩溃
back
-
作用:访问队尾元素(最后加入的元素),返回这个元素的引用
-
详细说明 :返回队列中最后进入的那个元素。与
front相反,back查看的是最近添加的元素。同样,这个操作只查看不删除元素,对空队列调用也会导致未定义行为
cpp
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<int> numbers;
std::cout << "=== front() 和 back() 基本示例 ===" << std::endl;
// 添加元素
numbers.push(10);
numbers.push(20);
numbers.push(30);
numbers.push(40);
numbers.push(50);
std::cout << "添加元素: 10, 20, 30, 40, 50" << std::endl;
// 查看队头和队尾
std::cout << "\n队列当前状态:" << std::endl;
std::cout << "队头 (front): " << numbers.front() << " (最先进入的元素)" << std::endl;
std::cout << "队尾 (back): " << numbers.back() << " (最后进入的元素)" << std::endl;
std::cout << "队列大小: " << numbers.size() << std::endl;
// 修改队头元素
std::cout << "\n修改队头元素为 100:" << std::endl;
numbers.front() = 100;
std::cout << "新的队头: " << numbers.front() << std::endl;
// 修改队尾元素
std::cout << "\n修改队尾元素为 500:" << std::endl;
numbers.back() = 500;
std::cout << "新的队尾: " << numbers.back() << std::endl;
// 处理队列
std::cout << "\n处理队列 (FIFO):" << std::endl;
while (!numbers.empty()) {
std::cout << "处理: " << numbers.front() << " (队尾是: " << numbers.back() << ")" << std::endl;
numbers.pop();
}
return 0;
}

push
-
作用:插入元素到队尾
-
详细说明:将一个新元素添加到队列的末尾。新加入的元素会成为新的队尾元素,按照先进先出的规则,它将在所有已有元素之后被处理
-
使用场景:将新的任务、消息或数据加入等待队列
cpp
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
// 使用 push 添加元素到队尾
q.push(10);
q.push(20);
q.push(30);
std::cout << "队列大小: " << q.size() << std::endl;
std::cout << "队头元素: " << q.front() << std::endl; // 10
std::cout << "队尾元素: " << q.back() << std::endl; // 30
return 0;
}

emplace
-
作用:在队尾直接构造元素(原地构造)
-
详细说明 :这是一个比
push更高效的操作。它直接在队列的尾部位置构造新元素,避免了先创建临时对象再复制或移动的过程。特别是对于复杂的对象类型,这个操作可以显著提高性能 -
使用场景:向队列中添加构造成本较高的复杂对象
cpp
#include <iostream>
#include <queue>
#include <string>
class Person {
public:
Person(std::string name, int age) : name(name), age(age) {
std::cout << "构造 Person: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name), age(other.age) {
std::cout << "拷贝构造 Person: " << name << std::endl;
}
// 移动构造函数
Person(Person&& other) noexcept : name(std::move(other.name)), age(other.age) {
std::cout << "移动构造 Person: " << name << std::endl;
}
void print() const {
std::cout << "姓名: " << name << ", 年龄: " << age << std::endl;
}
private:
std::string name;
int age;
};
int main() {
std::queue<Person> q;
std::cout << "使用 push:" << std::endl;
Person p1("Alice", 25);
q.push(p1); // 这里会调用拷贝构造函数
std::cout << "\n使用 push 移动语义:" << std::endl;
Person p2("Bob", 30);
q.push(std::move(p2)); // 这里会调用移动构造函数
std::cout << "\n使用 emplace:" << std::endl;
q.emplace("Charlie", 35); // 直接在队列中构造,没有拷贝或移动
std::cout << "\n队列中的元素:" << std::endl;
// 注意:queue 没有迭代器,所以只能通过 front 和 pop 来访问
while (!q.empty()) {
q.front().print();
q.pop();
}
return 0;
}

pop
-
作用:移除队头元素
-
详细说明 :将队列最前端的元素移除。注意:这个函数不返回被移除的元素值 ,只是简单地将它从队列中删除。如果需要获取被移除的元素值,必须先用
front获取值,再调用pop移除 -
使用场景:完成一个任务或处理完一条消息后,将其从队列中移出
cpp
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
// 添加元素
for (int i = 1; i <= 5; ++i) {
q.push(i * 10);
}
std::cout << "初始队列大小: " << q.size() << std::endl;
// 处理队列中的元素
while (!q.empty()) {
std::cout << "处理队头元素: " << q.front() << std::endl;
q.pop(); // 移除队头元素
std::cout << "移除后队列大小: " << q.size() << std::endl;
}
// 注意:对空队列调用 pop 是未定义行为
// q.pop(); // 错误!
return 0;
}

swap
-
作用:交换两个队列的内容
-
详细说明:将当前队列与另一个队列的所有元素进行交换。这个操作非常高效,因为它通常只交换两个队列的内部数据结构指针,而不是逐个复制所有元素
-
使用场景:快速切换两个工作队列,或者在需要清空队列时使用交换技巧
cpp
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> queueA;
std::queue<std::string> queueB;
// 初始化queueA
queueA.push("任务A1");
queueA.push("任务A2");
queueA.push("任务A3");
// 初始化queueB
queueB.push("任务B1");
queueB.push("任务B2");
std::cout << "交换前:" << std::endl;
std::cout << "queueA 大小: " << queueA.size()
<< ", 队头: " << (queueA.empty() ? "空" : queueA.front())
<< ", 队尾: " << (queueA.empty() ? "空" : queueA.back()) << std::endl;
std::cout << "queueB 大小: " << queueB.size()
<< ", 队头: " << (queueB.empty() ? "空" : queueB.front())
<< ", 队尾: " << (queueB.empty() ? "空" : queueB.back()) << std::endl;
// 交换两个队列的内容
queueA.swap(queueB);
std::cout << "\n交换后:" << std::endl;
std::cout << "queueA 大小: " << queueA.size()
<< ", 队头: " << (queueA.empty() ? "空" : queueA.front())
<< ", 队尾: " << (queueA.empty() ? "空" : queueA.back()) << std::endl;
std::cout << "queueB 大小: " << queueB.size()
<< ", 队头: " << (queueB.empty() ? "空" : queueB.front())
<< ", 队尾: " << (queueB.empty() ? "空" : queueB.back()) << std::endl;
// 使用std::swap也可以
std::cout << "\n使用std::swap再次交换:" << std::endl;
std::swap(queueA, queueB);
std::cout << "queueA 队头: " << (queueA.empty() ? "空" : queueA.front()) << std::endl;
std::cout << "queueB 队头: " << (queueB.empty() ? "空" : queueB.front()) << std::endl;
return 0;
}

三.priority_queue(优先级队列)
请注意,queue是先进先出(FIFO)的队列,而priority_queue是优先级队列(元素按优先级排序,每次访问优先级最高的元素)。下面我们从多个维度进行比较。
-
访问顺序:
-
queue:严格遵循先进先出(FIFO)的顺序。最先插入的元素最先被移除。
-
priority_queue:元素被赋予优先级。每次访问和移除的都是当前队列中优先级最高的元素(默认是最大的元素,即最大堆)。插入顺序并不影响移除顺序。
-
-
底层实现:
-
queue:默认使用deque作为底层容器,但也可以使用list或其他满足queue操作的容器。
-
priority_queue:默认使用vector作为底层容器,并且使用堆算法(heap)来维护元素的优先级顺序。也可以使用deque,但vector通常性能更好。
-
3.1.构造函数
那么对于这个优先级队列,其实是有两种构造函数的
也就是下面这2种:
cpp
// 初始化构造函数(最常用)
explicit priority_queue(const Compare& comp = Compare(),
const Container& ctnr = Container());
// 范围构造函数
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last,
const Compare& comp = Compare(),
const Container& ctnr = Container());
首先我们看看这个初始化构造函数的
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional> // for greater
int main()
{
std::cout << "=== priority_queue 初始化构造函数示例 ===\n" << std::endl;
// 1.1 默认构造 - 最大堆(最大元素在顶部)
std::priority_queue<int> pq1;
std::cout << "1. 默认构造的最大堆 priority_queue" << std::endl;
pq1.push(1);
pq1.push(2);
pq1.push(3);
pq1.push(4);
std::cout << " 顶部元素: " << pq1.top() << std::endl; // 应该是 4
// 1.2 指定比较函数 - 最小堆(最小元素在顶部)
std::priority_queue<int, std::vector<int>, std::greater<int>> pq2;
std::cout << "2. 最小堆 priority_queue (使用 std::greater<int>)" << std::endl;
pq2.push(1);
pq2.push(2);
pq2.push(3);
pq2.push(4);
std::cout << " 顶部元素: " << pq2.top() << std::endl; // 应该是 1
// 1.3 使用已有容器初始化(注意:需要手动建堆)
std::vector<int> vec = {30, 10, 50, 20, 40};
std::priority_queue<int> pq3(std::less<int>(), vec);//最大堆
std::cout << "3. 使用 vector 初始化的 priority_queue" << std::endl;
std::cout << " 容器初始内容: ";
for (int num : vec) std::cout << num << " ";
std::cout << std::endl;
std::cout << " 优先队列顶部: " << pq3.top() << std::endl; // 应该是 50(最大堆)
// 1.4 自定义比较函数
struct CustomCompare {
bool operator()(int a, int b) {
// 按照个位数大小排序(个位数小的优先级高)
return (a % 10) > (b % 10);
}
};
std::priority_queue<int, std::vector<int>, CustomCompare> pq4;
pq4.push(25);
pq4.push(17);
pq4.push(32);
pq4.push(46);
std::cout << "\n4. 自定义比较函数的 priority_queue" << std::endl;
std::cout << " 比较规则:按个位数从小到大排序" << std::endl;
std::cout << " 元素: 25, 17, 32, 46" << std::endl;
std::cout << " 顶部元素: " << pq4.top() << std::endl; // 应该是 32(个位是2)
return 0;
}

然后我们看这个范围构造函数的
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <list>
#include <array>
#include <functional>
int main() {
std::cout << "=== priority_queue 范围构造函数示例 ===\n" << std::endl;
// 2.1 使用 vector 的范围初始化
std::vector<int> vec = {70, 20, 90, 10, 50};
std::priority_queue<int> pq1(vec.begin(), vec.end());
std::cout << "1. 使用 vector 范围构造的最大堆:" << std::endl;
std::cout << " 原始数据: ";
for (int num : vec) std::cout << num << " ";
std::cout << "\n 顶部元素(最大): " << pq1.top() << std::endl; // 90
// 2.2 使用 list 的范围初始化最小堆
std::list<int> lst = {15, 35, 5, 45, 25};
std::priority_queue<int, std::vector<int>, std::greater<int>>
pq2(lst.begin(), lst.end());
std::cout << "\n2. 使用 list 范围构造的最小堆:" << std::endl;
std::cout << " 原始数据: ";
for (int num : lst) std::cout << num << " ";
std::cout << "\n 顶部元素(最小): " << pq2.top() << std::endl; // 5
// 2.3 使用数组的范围初始化
int arr[] = {100, 60, 80, 40, 120};
std::priority_queue<int> pq3(arr, arr + 5);
std::cout << "\n3. 使用数组范围构造的最大堆:" << std::endl;
std::cout << " 原始数据: ";
for (int i = 0; i < 5; i++) std::cout << arr[i] << " ";
std::cout << "\n 顶部元素(最大): " << pq3.top() << std::endl; // 120
// 2.4 使用 array 的范围初始化
std::array<int, 6> arr2 = {88, 33, 66, 99, 11, 55};
std::priority_queue<int> pq4(arr2.begin(), arr2.end());
std::cout << "\n4. 使用 array 范围构造的最大堆:" << std::endl;
std::cout << " 原始数据: ";
for (int num : arr2) std::cout << num << " ";
std::cout << "\n 顶部元素(最大): " << pq4.top() << std::endl; // 99
// 2.5 结合比较函数和容器
std::vector<int> vec2 = {200, 150, 300, 100, 250};
// 使用范围和比较函数构造最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>>
pq5(vec2.begin(), vec2.end(), std::greater<int>());
std::cout << "\n5. 指定范围和比较函数的最小堆:" << std::endl;
std::cout << " 原始数据: ";
for (int num : vec2) std::cout << num << " ";
std::cout << "\n 顶部元素(最小): " << pq5.top() << std::endl; // 100
return 0;
}

3.3.常用操作
作为容器适配器,这个优先级队列也是提供了一些常用的接口
empty
-
作用:检查优先队列是否为空
-
详细说明 :判断优先队列中是否有元素。这是进行任何操作前的安全检查,因为对空优先队列调用
top()或pop()会导致未定义行为 -
使用场景:在尝试访问或弹出元素前检查队列状态
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
int main() {
std::cout << "=== priority_queue::empty() 方法示例 ===\n" << std::endl;
// 1.1 创建不同优先队列
std::priority_queue<int> maxHeap; // 最大堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // 最小堆
std::cout << "1. 初始状态检查:" << std::endl;
std::cout << "最大堆是否为空: " << (maxHeap.empty() ? "是" : "否") << std::endl;
std::cout << "最小堆是否为空: " << (minHeap.empty() ? "是" : "否") << std::endl;
// 1.2 添加元素后检查
maxHeap.push(10);
minHeap.push(10);
std::cout << "\n2. 添加元素后检查:" << std::endl;
std::cout << "最大堆是否为空: " << (maxHeap.empty() ? "是" : "否") << std::endl;
std::cout << "最小堆是否为空: " << (minHeap.empty() ? "是" : "否") << std::endl;
// 1.3 安全操作示例
std::cout << "\n3. 安全操作示例:" << std::endl;
// 错误示例:直接调用 top() 或 pop() 可能导致崩溃
// std::priority_queue<int> emptyQueue;
// int value = emptyQueue.top(); // 未定义行为!
// emptyQueue.pop(); // 未定义行为!
// 正确做法:先检查是否为空
std::priority_queue<int> safeQueue;
if (!safeQueue.empty()) {
std::cout << "顶部元素: " << safeQueue.top() << std::endl;
safeQueue.pop();
} else {
std::cout << "队列为空,无法访问顶部元素" << std::endl;
}
// 1.4 清空队列检查
std::cout << "\n4. 清空队列检查:" << std::endl;
while (!maxHeap.empty()) {
maxHeap.pop();
}
std::cout << "清空后最大堆是否为空: " << (maxHeap.empty() ? "是" : "否") << std::endl;
return 0;
}

size
-
作用:返回优先队列中当前元素的个数
-
详细说明:返回队列包含的元素数量。这个值随着元素的插入和删除动态变化
-
使用场景:监控队列规模,控制内存使用或处理进度
cpp
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::cout << "=== priority_queue::size() 方法示例 ===\n" << std::endl;
std::priority_queue<int> pq;
// 2.1 初始大小
std::cout << "1. 初始队列大小: " << pq.size() << std::endl;
// 2.2 添加元素
std::cout << "\n2. 添加元素:" << std::endl;
for (int i = 1; i <= 5; i++) {
pq.push(i * 10);
std::cout << "push(" << i * 10 << ") 后,队列大小: " << pq.size() << std::endl;
}
// 2.3 删除元素
std::cout << "\n3. 删除元素:" << std::endl;
while (!pq.empty()) {
std::cout << "当前大小: " << pq.size()
<< ",顶部元素: " << pq.top() << std::endl;
pq.pop();
}
// 2.4 批量操作与大小监控
std::cout << "\n4. 批量操作与大小监控:" << std::endl;
std::vector<int> data = {25, 15, 35, 5, 45, 20, 30, 10, 40};
for (int value : data) {
pq.push(value);
std::cout << "添加 " << value << ",当前大小: " << pq.size();
if (!pq.empty()) {
std::cout << ",当前顶部: " << pq.top();
}
std::cout << std::endl;
}
std::cout << "\n最终队列大小: " << pq.size() << std::endl;
return 0;
}


top
-
作用:访问优先级最高的元素(堆顶元素),注意:不能通过top()函数来堆这个优先级最高的元素进行修改。
-
返回优先队列中优先级最高的元素(堆顶元素),但不删除它。默认情况下(最大堆)返回的是当前队列中的最大元素。
-
详细说明 :返回优先队列中优先级最高 的元素。注意:优先队列不保证先进先出 ,而是保证每次访问的都是当前队列中优先级最高的元素。默认情况下是最大元素(最大堆)。这个操作只查看不删除元素
-
使用场景:查看下一个要处理的高优先级任务
cpp
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::cout << "=== priority_queue::top() 基本示例 ===\n" << std::endl;
// 1. 最大堆(默认)- 顶部是最大元素
std::priority_queue<int> maxHeap;
maxHeap.push(30);
maxHeap.push(10);
maxHeap.push(50);
maxHeap.push(20);
maxHeap.push(40);
std::cout << "1. 最大堆示例:" << std::endl;
std::cout << "元素: 30, 10, 50, 20, 40" << std::endl;
std::cout << "顶部元素(最大): " << maxHeap.top() << std::endl; // 输出: 50
// 2. 最小堆 - 顶部是最小元素
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(30);
minHeap.push(10);
minHeap.push(50);
minHeap.push(20);
minHeap.push(40);
std::cout << "\n2. 最小堆示例:" << std::endl;
std::cout << "元素: 30, 10, 50, 20, 40" << std::endl;
std::cout << "顶部元素(最小): " << minHeap.top() << std::endl; // 输出: 10
// 3. 查看但不删除
std::cout << "\n3. 查看但不删除演示:" << std::endl;
std::priority_queue<int> pq;
pq.push(100);
pq.push(200);
pq.push(300);
std::cout << "初始顶部: " << pq.top() << std::endl; // 300
std::cout << "再次查看顶部: " << pq.top() << std::endl; // 还是300
std::cout << "队列大小: " << pq.size() << std::endl; // 3
return 0;
}

push
-
作用:插入元素到优先队列
-
详细说明:将一个新元素添加到优先队列中。新元素的位置不是固定的,而是根据其优先级自动调整到合适位置(堆化过程)
-
使用场景:将新任务或数据加入优先队列,让系统按优先级处理
cpp
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::cout << "=== priority_queue::push() 方法示例 ===\n" << std::endl;
// 1.1 最大堆(默认)
std::priority_queue<int> maxHeap;
std::cout << "1. 最大堆 push 示例:" << std::endl;
maxHeap.push(30);
std::cout << "push(30) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
maxHeap.push(10);
std::cout << "push(10) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
maxHeap.push(50);
std::cout << "push(50) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
maxHeap.push(20);
std::cout << "push(20) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
// 1.2 最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
std::cout << "\n2. 最小堆 push 示例:" << std::endl;
minHeap.push(30);
std::cout << "push(30) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
minHeap.push(10);
std::cout << "push(10) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
minHeap.push(50);
std::cout << "push(50) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
// 1.3 批量 push 示例
std::cout << "\n3. 批量 push 示例:" << std::endl;
std::priority_queue<int> batchHeap;
std::vector<int> numbers = {25, 15, 35, 5, 45, 20, 30};
for (int num : numbers) {
batchHeap.push(num);
std::cout << "push(" << num << ") 后,顶部: " << batchHeap.top()
<< ",大小: " << batchHeap.size() << std::endl;
}
return 0;
}

emplace
-
作用:在优先队列中直接构造元素(原地构造)
-
详细说明 :比
push更高效的操作,直接在优先队列中构造新元素,避免了创建临时对象再复制的开销。元素构造后会根据优先级自动调整位置 -
使用场景:向优先队列中添加构造复杂的对象,提高性能
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
int main() {
std::cout << "=== priority_queue::emplace() 基础数据类型示例 ===\n" << std::endl;
// 1.1 最大堆 emplace 示例
std::priority_queue<int> maxHeap;
std::cout << "1. 最大堆 emplace 示例:" << std::endl;
maxHeap.emplace(30);
std::cout << "emplace(30) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
maxHeap.emplace(10);
std::cout << "emplace(10) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
maxHeap.emplace(50);
std::cout << "emplace(50) 后,顶部: " << maxHeap.top()
<< ",大小: " << maxHeap.size() << std::endl;
// 1.2 最小堆 emplace 示例
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
std::cout << "\n2. 最小堆 emplace 示例:" << std::endl;
minHeap.emplace(30);
std::cout << "emplace(30) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
minHeap.emplace(10);
std::cout << "emplace(10) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
minHeap.emplace(50);
std::cout << "emplace(50) 后,顶部: " << minHeap.top()
<< ",大小: " << minHeap.size() << std::endl;
return 0;
}

pop
-
作用:移除优先级最高的元素(堆顶元素)
-
详细说明 :删除当前优先队列中优先级最高的元素。注意:这个函数不返回被移除的元素值,只是将它从队列中删除。移除后,队列会自动重新调整,使新的最高优先级元素上升到顶部
-
使用场景:处理完一个高优先级任务后,将其从队列中移除
cpp
#include <iostream>
#include <queue>
#include <chrono>
#include <thread>
int main() {
std::cout << "=== priority_queue::pop() 方法示例 ===\n" << std::endl;
std::priority_queue<int> pq;
// 添加元素
std::cout << "1. 添加元素到优先队列:" << std::endl;
for (int i = 1; i <= 8; i++) {
int value = i * 10;
pq.push(value);
std::cout << "push(" << value << "),当前顶部: " << pq.top()
<< ",大小: " << pq.size() << std::endl;
}
std::cout << "\n2. 开始 pop 操作 (从高到低移除):" << std::endl;
int count = 0;
while (!pq.empty()) {
count++;
// 先查看顶部元素
std::cout << "第" << count << "次 pop 前:" << std::endl;
std::cout << " 顶部元素: " << pq.top() << std::endl;
std::cout << " 队列大小: " << pq.size() << std::endl;
// 移除顶部元素
pq.pop();
// 查看移除后的状态
if (!pq.empty()) {
std::cout << " pop 后新的顶部: " << pq.top()
<< ",剩余大小: " << pq.size() << std::endl;
} else {
std::cout << " pop 后队列为空" << std::endl;
}
std::cout << std::endl;
}
std::cout << "3. 错误使用示例:" << std::endl;
std::priority_queue<int> emptyQueue;
// 错误1: 对空队列调用 pop()
// emptyQueue.pop(); // 未定义行为!可能导致崩溃
// 错误2: 试图获取 pop() 的返回值
// int value = emptyQueue.pop(); // 错误!pop() 不返回值
std::cout << "正确做法:先用 empty() 检查,再 top() 获取值,最后 pop()" << std::endl;
return 0;
}



swap
-
作用:交换两个优先队列的内容
-
详细说明:将当前优先队列与另一个优先队列的所有元素进行交换。这个操作通常只交换内部数据结构指针,而不是逐个复制元素,因此非常高效
-
使用场景:快速切换两个工作队列,或在特定算法中交换数据
cpp
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::cout << "=== priority_queue::swap() 基本示例 ===\n" << std::endl;
// 1. 创建两个优先队列
std::priority_queue<int> pq1;
std::priority_queue<int> pq2;
// 填充数据
pq1.push(10);
pq1.push(20);
pq1.push(30);
pq2.push(100);
pq2.push(200);
pq2.push(300);
pq2.push(400);
std::cout << "交换前:" << std::endl;
std::cout << "pq1 大小: " << pq1.size()
<< ", 顶部: " << (pq1.empty() ? "空" : std::to_string(pq1.top())) << std::endl;
std::cout << "pq2 大小: " << pq2.size()
<< ", 顶部: " << (pq2.empty() ? "空" : std::to_string(pq2.top())) << std::endl;
// 交换两个优先队列
pq1.swap(pq2);
std::cout << "\n交换后:" << std::endl;
std::cout << "pq1 大小: " << pq1.size()
<< ", 顶部: " << (pq1.empty() ? "空" : std::to_string(pq1.top())) << std::endl;
std::cout << "pq2 大小: " << pq2.size()
<< ", 顶部: " << (pq2.empty() ? "空" : std::to_string(pq2.top())) << std::endl;
// 也可以使用 std::swap
std::cout << "\n使用 std::swap 再次交换:" << std::endl;
std::swap(pq1, pq2);
std::cout << "pq1 顶部: " << (pq1.empty() ? "空" : std::to_string(pq1.top())) << std::endl;
std::cout << "pq2 顶部: " << (pq2.empty() ? "空" : std::to_string(pq2.top())) << std::endl;
return 0;
}
