跳转到内容
新建笔记

C++ 适配器:栈队列、反向迭代与调用组合

适配器复用已有对象,并提供另一种受约束的接口。容器适配器限制访问方式,迭代器适配器改变遍历或写入动作,函数适配器组合调用行为。本页采用C++17。

1. 栈、队列与优先队列

跳转到“1. 栈、队列与优先队列”
适配器访问规则常用接口默认底层容器
std::stack<T>后进先出。push/emplace/top/pop/empty/sizedeque<T>
std::queue<T>先进先出。push/emplace/front/back/pop/empty/sizedeque<T>
std::priority_queue<T>先取比较规则确定的最高优先项。push/emplace/top/pop/empty/sizevector<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插入会按自身规则排序并拒绝重复键。适配器只负责调用接口,不绕过容器语义。

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与函数对象。