在 C++ STL 的体系中,容器适配器(Container Adapter)是一类特殊的 "容器"------ 它们并非原生容器,而是通过适配底层容器的接口,封装出特定的访问逻辑。本文将从容器适配器的核心设计思想出发,详解 stack、queue、priority_queue 的实现原理,同时拆解仿函数、模板特化等关键配套技术,带你吃透 STL 适配器的设计精髓。
一、容器适配器的核心思想
适配器是一种经典的设计模式,核心是 "接口转换":将一个类的接口转换成客户期望的另一个接口。STL 中的 stack、queue、priority_queue 均属于适配器,它们不直接实现数据存储,而是复用 deque、vector、list 等原生容器的底层能力,仅封装上层的访问规则。
比如 stack 的 "后进先出"、queue 的 "先进先出"、priority_queue 的 "按优先级访问",本质都是对底层容器接口的筛选和重组 ------ 这也是适配器模式的核心价值:复用现有逻辑,定制特定行为。
二、stack 与 queue:基于 deque 的适配实现
stack(栈)和 queue(队列)是最基础的适配器,默认选择 deque 作为底层容器,这与 deque 的特性密切相关。
1. deque:vector 与 list 的 "缝合怪"
deque(双端队列)平衡了 vector 和 list 的优缺点,成为 stack/queue 的默认适配容器:
|-----------|--------------|----------|-------------------|
| 特性 | vector | list | deque |
| 头插 / 尾插效率 | 尾插优、头插 O (N) | 均为 O (1) | 均为 O (1)(效率更高) |
| 随机访问效率 | O (1)(最优) | O(N) | O (1)(略慢于 vector) |
| 空间扩容 | 拷贝 + 浪费内存 | 按需申请 | 分段扩容(无拷贝) |
deque 的 "分段连续" 结构,既支持高效的头尾操作,又避免了 vector 扩容的代价、list 随机访问的低效,因此成为 stack/queue 的最优选择。
2. stack:后进先出的封装
stack 将底层容器的 "尾部" 作为栈顶,仅暴露 push/pop/top 等核心接口,屏蔽其他无关操作:
cpp
#pragma once
#include <deque>
namespace bit
{
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& x) { _con.push_back(x); } // 尾插=入栈
void pop() { _con.pop_back(); } // 尾删=出栈
const T& top() const { return _con.back(); } // 取尾部=栈顶
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
private:
Container _con; // 复用底层容器的存储逻辑
};
}
stack 的适配逻辑极其简洁:所有操作都映射到底层容器的 "尾部操作",且支持替换底层容器(如 vector、list),只要容器提供push_back/pop_back/back接口即可。
3. queue:先进先出的封装
queue 需要 "尾部插入、头部删除",因此适配的容器需支持push_back/pop_front------ 这也是 queue 默认不用 vector 的原因(vector 无pop_front接口):
cpp
#pragma once
#include <deque>
namespace bit
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x) { _con.push_back(x); } // 尾插=入队
void pop() { _con.pop_front(); } // 头删=出队
const T& front() const { return _con.front(); } // 队头
const T& back() const { return _con.back(); } // 队尾
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
private:
Container _con;
};
}
三、priority_queue:基于堆与仿函数的优先级队列
priority_queue(优先级队列)是更复杂的适配器:底层基于堆结构(默认 vector 存储),通过仿函数控制优先级规则。
1. 堆的核心操作:向上 / 向下调整
priority_queue 的核心是维护一个堆(默认大顶堆),插入 / 删除元素时需通过AdjustUp/AdjustDown调整堆结构:
cpp
#pragma once
#include <vector>
template<class T>
class Less { // 默认大顶堆的比较规则
public:
bool operator()(const T& x, const T& y) { return x < y; }
};
namespace ssp
{
template<class T, class Container = vector<T>, class Compare = Less<T>>
class priority_queue
{
public:
// 向上调整(插入元素后)
void AdjustUp(int child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child])) // 父 < 子,交换(大顶堆)
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
// 向下调整(删除堆顶后)
void AdjustDown(int parent)
{
Compare com;
size_t child = 2 * parent + 1;
while (child < _con.size())
{
// 找左右孩子中优先级更高的那个
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
child += 1;
// 父 < 子,交换
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = 2 * parent + 1;
}
else break;
}
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1); // 尾插后调整
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]); // 堆顶与堆尾交换
_con.pop_back(); // 删堆尾(原堆顶)
AdjustDown(0); // 调整新堆顶
}
const T& top() { return _con[0]; } // 堆顶=优先级最高元素
private:
Container _con;
};
}
2. 仿函数:定制优先级规则
仿函数是**"重载了 operator () 的类"**,其对象可像函数一样调用,核心作用是封装比较逻辑 ------ 这让 priority_queue 支持自定义优先级。
(1)仿函数的基础定义
cpp
template<class T>
class Greater { // 小顶堆的比较规则
public:
bool operator()(const T& x, const T& y) { return x > y; }
};
// 使用小顶堆
priority_queue<int, vector<int>, Greater<int>> pq;
(2)自定义类型的优先级控制
当 priority_queue 存储自定义类型(如Date*)时,默认按指针地址比较,需自定义仿函数:
cpp
class Date {
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
bool operator<(const Date& d) const { // 自定义比较逻辑
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
private:
int _year, _month, _day;
};
// 自定义仿函数:按Date内容比较指针
class DateLess {
public:
bool operator()(Date* p1, Date* p2) { return *p1 < *p2; }
};
// 使用:按Date内容维护大顶堆
priority_queue<Date*, vector<Date*>, DateLess> q;
3. 仿函数的通用场景:定制算法逻辑
仿函数不仅用于 priority_queue,还可用于定制算法规则(如排序):
cpp
template<class Compare>
void BubbleSort(int* a, int n, Compare com)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n - i; j++)
{
if (com(a[j], a[j - 1])) // 仿函数控制比较规则
{
swap(a[j - 1], a[j]);
flag = 1;
}
}
if (flag == 0) break;
}
}
// 升序排序(Less)、降序排序(Greater)
int a[] = {9,1,2,6,5,7,3,4};
BubbleSort(a, sizeof(a)/sizeof(int), Less<int>()); // 升序
BubbleSort(a, sizeof(a)/sizeof(int), Greater<int>()); // 降序