
在C++中,容器适配器(Container Adaptors)是一种特殊的容器类,它们提供了特定的接口来操作底层容器。容器适配器本身并不直接存储元素,而是通过封装其他容器(如vector、deque或list)来提供特定的功能。C++标准库提供了三种主要的容器适配器:stack(栈)、queue(队列)和priority_queue(优先队列)。
stack(栈)push(入栈)、pop(出栈)、top(访问栈顶元素)、empty(判断是否为空)、size(返回元素个数)。deque,也可指定为vector或list。可在第二个参数给出。queue(队列)push(入队)、pop(出队)、front(访问队首元素)、back(访问队尾元素)、empty、size。deque,也可指定为list。priority_queue(优先队列)push(插入元素)、pop(删除优先级最高的元素)、top(访问优先级最高的元素)、empty、size。vector,结合heap算法实现。容器适配器可通过模板参数指定底层容器:
// 使用vector作为stack的底层容器
stack<int, vector<int>> stackWithVector;
// 使用list作为queue的底层容器
queue<int, list<int>> queueWithList;容器适配器 | 数据结构特点 | 默认底层容器 | 适用场景 |
|---|---|---|---|
stack | LIFO | deque | 递归模拟、表达式求值 |
queue | FIFO | deque | 任务调度、广度优先搜索 |
priority_queue | 优先级排序 | vector+heap | 任务调度(按优先级)、贪心算法 |
选择合适的容器适配器可以提高代码的可读性和性能。
在 C++ 中,stack 是一种容器适配器,它提供了一种后进先出(Last In First Out,LIFO)的数据结构。它基于底层容器(默认是 std::deque,也可以是 std::vector 或 std::list 等)来存储元素,但只允许在容器的一端(称为栈顶)进行操作。
top():返回栈顶元素的引用。如果栈为空,调用 top() 是未定义行为。empty():检查栈是否为空,如果为空返回 true,否则返回 false。size():返回栈中元素的数量。push(const T& value):将一个新元素压入栈顶。pop():移除栈顶元素。注意,pop() 不返回被移除的元素,如果栈为空,调用 pop() 是未定义行为。emplace(Args&&... args):在栈顶构造一个新元素,避免了额外的拷贝或移动操作。stack 的底层容器默认是 std::deque,但可以通过模板参数指定其他容器,如 std::vector 或 std::list。底层容器的选择会影响 stack 的性能和内存使用方式。
在使用 stack 之前,需要包含头文件 <stack>:
#include <stack>#include <iostream>
#include <stack>
int main() {
// 创建一个空的栈
std::stack<int> mystack;
// 向栈中压入元素
mystack.push(10);
mystack.push(20);
mystack.push(30);
// 访问栈顶元素
std::cout << "栈顶元素是: " << mystack.top() << std::endl;
// 检查栈是否为空
std::cout << "栈是否为空: " << (mystack.empty() ? "是" : "否") << std::endl;
// 输出栈的大小
std::cout << "栈的大小是: " << mystack.size() << std::endl;
// 弹出栈顶元素
mystack.pop();
// 再次访问栈顶元素
std::cout << "弹出一个元素后,栈顶元素是: " << mystack.top() << std::endl;
return 0;
}stack 可以存储任何类型的元素,包括自定义类型。自定义类型需要满足底层容器的要求,例如提供默认构造函数、拷贝构造函数等。
#include <iostream>
#include <stack>
struct Person {
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {}
};
int main() {
std::stack<Person> personStack;
personStack.push(Person("Alice", 25));
personStack.push(Person("Bob", 30));
std::cout << "栈顶元素是: " << personStack.top().name << ", 年龄: " << personStack.top().age << std::endl;
return 0;
}可以通过模板参数指定底层容器。例如,使用 std::vector 作为底层容器:
#include <iostream>
#include <stack>
#include <vector>
int main() {
std::stack<int, std::vector<int>> mystack;
mystack.push(10);
mystack.push(20);
std::cout << "栈顶元素是: " << mystack.top() << std::endl;
return 0;
}不同的底层容器会影响 stack 的性能和内存使用。例如:
std::deque(默认):支持快速的插入和删除操作,内存分配相对灵活。std::vector:内存连续,访问速度快,但插入和删除操作可能涉及内存重新分配。std::list:支持双向迭代,插入和删除操作非常高效,但访问速度相对较慢。在 C++ 中,queue 是一种容器适配器,它提供了一种先进先出(First In First Out,FIFO)的数据结构。它基于底层容器(默认是 std::deque,也可以是 std::list 或 std::vector 等)来存储元素,但只允许在容器的一端(队尾)插入元素,在另一端(队首)删除元素。
front():返回队首元素的引用。如果队列为空,调用 front() 是未定义行为。back():返回队尾元素的引用。如果队列为空,调用 back() 是未定义行为。empty():检查队列是否为空,如果为空返回 true,否则返回 false。size():返回队列中元素的数量。push(const T& value):将一个新元素插入到队尾。pop():移除队首元素。注意,pop() 不返回被移除的元素,如果队列为空,调用 pop() 是未定义行为。emplace(Args&&... args):在队尾构造一个新元素,避免了额外的拷贝或移动操作。queue 的底层容器默认是 std::deque,但可以通过模板参数指定其他容器,如 std::list 或 std::vector。底层容器的选择会影响 queue 的性能和内存使用方式。
在使用 queue 之前,需要包含头文件 <queue>:
#include <queue>#include <iostream>
#include <queue>
int main() {
// 创建一个空的队列
std::queue<int> myqueue;
// 向队列中插入元素
myqueue.push(10);
myqueue.push(20);
myqueue.push(30);
// 访问队首元素
std::cout << "队首元素是: " << myqueue.front() << std::endl;
// 访问队尾元素
std::cout << "队尾元素是: " << myqueue.back() << std::endl;
// 检查队列是否为空
std::cout << "队列是否为空: " << (myqueue.empty() ? "是" : "否") << std::endl;
// 输出队列的大小
std::cout << "队列的大小是: " << myqueue.size() << std::endl;
// 移除队首元素
myqueue.pop();
// 再次访问队首元素
std::cout << "移除一个元素后,队首元素是: " << myqueue.front() << std::endl;
return 0;
}queue 可以存储任何类型的元素,包括自定义类型。自定义类型需要满足底层容器的要求,例如提供默认构造函数、拷贝构造函数等。
#include <iostream>
#include <queue>
struct Person {
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {}
};
int main() {
std::queue<Person> personQueue;
personQueue.push(Person("Alice", 25));
personQueue.push(Person("Bob", 30));
std::cout << "队首元素是: " << personQueue.front().name << ", 年龄: " << personQueue.front().age << std::endl;
return 0;
}可以通过模板参数指定底层容器。例如,使用 std::list 作为底层容器:
#include <iostream>
#include <queue>
#include <list>
int main() {
std::queue<int, std::list<int>> myqueue;
myqueue.push(10);
myqueue.push(20);
std::cout << "队首元素是: " << myqueue.front() << std::endl;
return 0;
}不同的底层容器会影响 queue 的性能和内存使用。例如:
std::deque(默认):支持快速的插入和删除操作,内存分配相对灵活。std::list:支持双向迭代,插入和删除操作非常高效,但访问速度相对较慢。std::vector:内存连续,访问速度快,但插入和删除操作可能涉及内存重新分配。C++ 标准库还提供了 std::priority_queue,它是一种特殊的队列,元素按照优先级顺序排列。默认情况下,优先级最高的元素(最大值)会被优先处理。
top():返回优先级最高的元素的引用。如果优先队列为空,调用 top() 是未定义行为。empty():检查优先队列是否为空,如果为空返回 true,否则返回 false。size():返回优先队列中元素的数量。push(const T& value):将一个新元素插入到优先队列中,并根据优先级重新排序。pop():移除优先级最高的元素。注意,pop() 不返回被移除的元素,如果优先队列为空,调用 pop() 是未定义行为。可以通过提供自定义比较函数来改变优先级的定义。例如,使用小顶堆(优先级最低的元素优先):
#include <iostream>
#include <queue>
#include <functional> // for std::greater
int main() {
// 默认是大顶堆(优先级最高的元素优先)
std::priority_queue<int> maxHeap;
maxHeap.push(10);
maxHeap.push(20);
maxHeap.push(30);
std::cout << "优先级最高的元素是: " << maxHeap.top() << std::endl;
// 自定义小顶堆(优先级最低的元素优先)
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(10);
minHeap.push(20);
minHeap.push(30);
std::cout << "优先级最低的元素是: " << minHeap.top() << std::endl;
return 0;
}std::deque(双端队列,发音为“deck”)是 C++ 标准模板库(STL)中的一种序列容器,它结合了栈和队列的特性,支持在两端(队首和队尾)高效地插入和删除元素。deque 提供了灵活的元素管理方式,同时保持了相对高效的随机访问能力。

deque 非常适合需要在两端频繁操作的场景。deque 在某些操作上不如 std::vector 那么高效,但提供了更大的灵活性。deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

那deque是如何借助其迭代器维护其假想连续的结构呢?

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;
queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。
但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为: