适配器复用已有对象,并提供另一种受约束的接口。容器适配器限制访问方式,迭代器适配器改变遍历或写入动作,函数适配器组合调用行为。本页采用C++17。
1. 栈、队列与优先队列
跳转到“1. 栈、队列与优先队列”| 适配器 | 访问规则 | 常用接口 | 默认底层容器 |
|---|---|---|---|
std::stack<T> | 后进先出。 | push/emplace/top/pop/empty/size | deque<T> |
std::queue<T> | 先进先出。 | push/emplace/front/back/pop/empty/size | deque<T> |
std::priority_queue<T> | 先取比较规则确定的最高优先项。 | push/emplace/top/pop/empty/size | vector<T> |
这三者不提供普通容器那样的 begin/end遍历接口。底层容器可以更换,但必须支持适配器要求的操作,例如queue需要头部删除,不能直接用vector替代默认deque。
pop()只移除、不返回元素;先读取或移动 front/top,再pop。访问端点和pop都要求非空。优先队列默认使用 std::less<T>,最大的值先出;改用 std::greater<T>时最小值先出。
#include <cassert>#include <functional>#include <queue>#include <stack>#include <vector>
int main(){ std::queue<int> queue; queue.push(10); queue.push(20); queue.emplace(30); assert(queue.front() == 10 && queue.back() == 30 && queue.size() == 3); const int first = queue.front(); queue.pop(); assert(first == 10 && queue.front() == 20); std::queue<int> empty; queue.swap(empty); assert(queue.empty() && empty.size() == 2);
std::stack<int> stack; stack.push(10); stack.push(20); assert(stack.top() == 20); stack.pop(); assert(stack.top() == 10);
std::priority_queue<int> largest; std::priority_queue<int, std::vector<int>, std::greater<int>> smallest; for (int value : {3, 1, 2}) { largest.push(value); smallest.push(value); } assert(largest.top() == 3 && smallest.top() == 1);}要清空queue,可以赋值为空队列,或与一个具名空队列交换。queue.swap(std::queue<int>())不能把临时对象绑定到所需的非const左值引用;std::queue<int>().swap(queue)是另一种合法写法,不能把调用两侧随意互换。
优先队列只保证top满足优先关系,并不让其底层存储完整排序;优先级相同时也不承诺按插入顺序取出。相等元素需要稳定次序时,应把序号纳入比较规则。
2. 反向迭代器与base位置
跳转到“2. 反向迭代器与base位置”反向迭代器的递增沿底层范围的反方向移动。rbegin()通常由 end()构造,rend()通常由 begin()构造。若反向迭代器可解引用,base()指向的是它所指元素的下一个正向位置,不是同一个元素。
#include <algorithm>#include <cassert>#include <iterator>#include <vector>
int main(){ std::vector<int> values{1, 2, 3}; auto reverse = values.rbegin(); assert(*reverse == 3 && reverse.base() == values.end()); ++reverse; assert(*reverse == 2 && *reverse.base() == 3);
const auto found = std::find(values.rbegin(), values.rend(), 2); if (found != values.rend()) values.erase(std::next(found).base()); assert((values == std::vector<int>{1, 3}));}由反向查找结果删除元素时,必须换算到正向的真实位置,并且不能对查找失败的 rend照搬该操作。删除之后,旧的反向迭代器也要按容器失效规则处理。
3. 插入迭代器让算法写入变成插入
跳转到“3. 插入迭代器让算法写入变成插入”| 工具 | 写入时调用 | 需要的底层能力 |
|---|---|---|
back_inserter(container) | push_back | 例如vector、deque、list。 |
front_inserter(container) | push_front | 例如deque、list;vector没有。 |
inserter(container, position) | 在位置附近调用insert并推进位置。 | 例如set或序列容器的insert接口。 |
#include <algorithm>#include <cassert>#include <deque>#include <iterator>#include <set>#include <vector>
int main(){ const std::vector<int> input{1, 2, 2, 3}; std::vector<int> appended; std::deque<int> prepended; std::set<int> unique; std::copy(input.begin(), input.end(), std::back_inserter(appended)); std::copy(input.begin(), input.end(), std::front_inserter(prepended)); std::copy(input.begin(), input.end(), std::inserter(unique, unique.end())); assert(appended == input); assert((prepended == std::deque<int>{3, 2, 2, 1})); assert(unique.size() == 3);}反复头插会反转输入的相对顺序;set插入会按自身规则排序并拒绝重复键。适配器只负责调用接口,不绕过容器语义。
4. bind与not_fn
跳转到“4. bind与not_fn”std::bind把部分参数绑定到可调用对象,使用占位符描述仍需调用者提供的参数。绑定时通常保存副本;要引用外部对象,用 std::ref并自行保证寿命。简单场景lambda往往更容易看清参数顺序和捕获方式。
#include <cassert>#include <functional>
int difference(int a, int b) { return a - b; }
int main(){ using namespace std::placeholders; const auto fromTen = std::bind(difference, 10, _1); const auto same = [](int value) { return difference(10, value); }; assert(fromTen(3) == 7 && same(3) == 7);
const auto even = [](int value) { return value % 2 == 0; }; const auto odd = std::not_fn(even); assert(even(4) && odd(3) && !odd(4));}旧 not1/not2有旧式适配器类型要求,在C++17弃用、C++20移除;现代代码用C++17的 not_fn或lambda。标准算术/比较/逻辑函数对象与捕获寿命统一见lambda与函数对象。