适配器
适配器(Adapter)本质上是一种“包装器”,它对已有的容器(vector、list、deque)或迭代器、函数对象进行封装,改变其接口形式,以用来满足特定场景的使用需求。
特点:
- 依赖底层容器/迭代器/函数对象,自身不提供独立的存储结构;
- 只修改接口,不改变底层对象的核心功能;
- 属于模板类,具备高度的通用性和灵活性。
分类:
- 容器适配器:将顺序容器(如 vector、deque、list)封装为栈、队列、优先队列等抽象数据结构。
- 迭代器适配器:对迭代器进行包装,改变其遍历行为或生成新的迭代器类型(如反向迭代器、插入迭代器、流迭代器等)。
- 函数适配器:将函数或函数对象进行组合、绑定参数或转换,使其能够适配到算法所需的接口(现代 C++ 中多由 lambda 和 std::bind 替代)。 核心思想是复用:通过组合现有组件,快速构建出新的功能,而不需要重新实现底层逻辑。
容器适配器
容器适配器是对STL基础容器(如deque、vector、list)进行封装,隐藏其部分接口,只暴露符合特定数据结构逻辑的接口,实现栈、队列、优先级队列等常用数据结构。 STL提供了三种常用容器适配器:
- stack:栈适配器,遵循“后进先出”(LIFO)的原则;
- queue:队列适配器,遵循“先进先出”(FIFO)的原则;
- priority_queue:优先级队列适配器,元素按优先级排序,优先级最高的元素先出队。
[! 注意] 容器适配器的底层容器可指定(部分适配器有默认底层容器),但需满足特定条件(如stack需要支持push_back、pop_back、back等操作)。
栈适配器
stack默认使用deque作为底层容器,也可指定vector或list作为底层容器(需支持push_back、pop_back、back、empty、size操作)。stack隐藏了底层容器的除“尾部操作”外的所有接口,只允许从尾部插入(push)、尾部删除(pop)、访问尾部元素(top)。
常用操作:
- push(const T& val):在栈顶插入元素;
- pop():删除栈顶元素(无返回值,需先判断非空);
- top():返回栈顶元素的引用(非空时使用);
- empty():判断栈是否为空,返回bool;
- size():返回栈中元素个数。
// 1、默认底层使用deque容器
stack<int> s1;
s1.push(1);
s1.push(2);
s1.push(3);
cout << "栈顶元素" << s1.top() << endl; // 3
cout << "栈大小" << s1.size() << endl; // 3
// 删除栈顶元素,无返回值
s1.pop();
// 2、指定栈底层容器
stack<int, vector<int>> s2;
s2.push(10);
s2.push(20);
s2.push(30);
cout << "栈顶元素" << s2.top() << endl; // 30
cout << "栈大小" << s2.size() << endl; // 3
使用时注意点:
- stack的pop()操作只删除元素,不返回元素,若需获取栈顶元素,需先调用top();
- 不能直接遍历stack,若需遍历,需先将元素弹出并暂存,遍历后再重新入栈(会破坏原栈结构);
- 底层容器若为vector,pop_back()效率较低(需移动后续元素),适合元素个数较少的场景;底层为deque时,效率更均衡。
队列适配器
queue默认使用deque作为底层容器,也可指定list作为底层容器(需支持push_back、pop_front、front、back、empty、size操作)。queue遵循“先进先出”原则,隐藏底层容器的其他接口,只允许从队尾插入(push)、队头删除(pop)、访问队头(front)和队尾(back)元素。
[! 注意] vector头部删除效率不高,因此队列不采用
常用操作:
- push(const T& val):在队尾插入元素;
- pop():删除队头元素(无返回值,需先判断非空);
- front():返回队头元素的引用;
- back():返回队尾元素的引用;
- empty():判断队列是否为空;
- size():返回队列中元素个数。
// 队列适配器
queue<int> q;
// 队尾插入元素
q.push(10);
q.push(20);
q.push(30);
cout << "队尾元素" << q.back() << endl; // 30
cout << "队首元素" << q.front() << endl; // 10
// 删除队首元素
q.pop();
cout << "队首元素" << q.front() << endl; // 20
使用注意:
- queue的pop()操作只删除队头元素,不返回元素,需先调用front()获取;
- 不能直接遍历queue,遍历需弹出元素暂存,会破坏原队列结构;
- 底层容器不能使用vector,因为vector不支持pop_front()(效率极低,STL不推荐)。
优先级队列适配器(priority_queue)
priority_queue默认使用vector作为底层容器,也可指定deque作为底层容器(需支持随机访问迭代器、push_back、pop_back、front等操作)。其核心是“堆排序”,底层容器会被维护成一个大根堆(默认),队头元素始终是优先级最高的元素(数值最大的元素)。 优先级可自定义:通过指定比较函数(仿函数),可实现小根堆(优先级最低的元素先出队)。
常用操作:
- push(const T& val):插入元素,自动调整堆结构,维持优先级;
- pop():删除队头(优先级最高)元素,自动调整堆结构;
- top():返回队头元素的引用(非空时使用);
- empty():判断队列是否为空;
- size():返回队列中元素个数。
使用注意:
- priority_queue的top()返回优先级最高的元素,默认是大根堆(数值大的优先);
- 插入和删除元素时,底层会自动调整堆结构,时间复杂度为O(log n);
- 自定义优先级时,需重载<运算符(默认)或使用仿函数,确保堆结构能正确维护;
- 不能直接遍历priority_queue,遍历需弹出元素,破坏原堆结构。
容器适配器的总结
- 不提供迭代器,不能使用 STL 算法直接操作。
- 底层容器必须支持某些操作:
stack要求back()、push_back()、pop_back();queue要求front()、back()、push_back()、pop_front();priority_queue要求随机访问迭代器和push_back()、pop_back(),以及堆算法。 - 通过封装,实现了数据结构的隔离,使代码意图更清晰。
迭代器适配器
迭代器适配器是对已有的迭代器(如vector::iterator、list::iterator)进行封装,改变其遍历方向、访问方式,使其满足特定的遍历需求。STL提供的迭代器适配器不改变迭代器的底层遍历逻辑,只修改其接口行为。 常用的迭代器适配器有3种:
- reverse_iterator:反向迭代器,将正向迭代器的遍历方向反转(从尾到头);
- insert_iterator:插入迭代器,将赋值操作(=)转化为插入操作,用于向容器中插入元素;
- stream_iterator:流迭代器,将输入/输出流(如cin、cout)封装为迭代器,实现流与容器的快速交互。
反向迭代器(reverse_iterator)
reverse_iterator基于正向迭代器实现,通过重载++、--运算符,将“正向遍历”转化为“反向遍历”。
使迭代器反向遍历容器。通过 rbegin() 和 rend() 获得。
[! 注意] 反向迭代器的++操作,等价于正向迭代器的--操作;反向迭代器的--操作,等价于正向迭代器的++操作。
vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 反向遍历
for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it)
{
cout << *it << " "; // 9 8 7 6 5 4 3 2 1 0
}
cout << endl;
插入迭代器(insert_iterator)
insert_iterator将赋值操作(*it = val)转化为容器的插入操作,避免直接使用 insert()方法的繁琐,常用于算法(如copy)中向容器插入元素。根据插入位置的不同, insert_iterator分为3种:
- back_insert_iterator:在容器尾部插入元素,要求容器支持push_back()(如vector、deque、list);
- front_insert_iterator:在容器头部插入元素,要求容器支持push_front()(如deque、list);
- insert_iterator:在容器指定位置插入元素,要求容器支持insert()方法(所有顺序容器均可)。
辅助函数:
back_inserter()、front_inserter()、inserter()方便创建对应迭代器
vector<int> src = { 0,1,2,3 };
vector<int> dst;
// back_insert_iterator
copy(src.begin(), src.end(), back_inserter(dst));
for (int i : dst)
{
cout << i << endl;
}
### 流迭代器(stream_iterator)
stream_iterator将输入流(如cin)或输出流(如cout)封装为迭代器,实现“迭代器操作”与“流操作”的无缝衔接。
- 输入流迭代器(istream_iterator)用于从流中读取数据;
- 输出流迭代器(ostream_iterator)用于向流中写入数据。
## 函数适配器
函数适配器(也叫函数对象适配器)是对STL函数对象(仿函数)进行封装、组合或修改,改变其参数个数、参数类型或返回值,使其满足算法(如sort、find_if)的参数要求。
STL提供的函数适配器主要依赖于\<functional\>头文件,常用的有:
- 绑定适配器:bind1st、bind2nd(C++11前),bind(C++11及以后,推荐使用);
- 否定适配器:not1、not2,用于否定函数对象的返回值;
- 函数指针适配器:ptr_fun,将普通函数指针转化为函数对象。
>[! 注意]
>C++11及以后,bind适配器功能更强大,可替代bind1st、bind2nd
### 绑定适配器
bind适配器的核心是“绑定函数对象/普通函数的参数”,可以固定部分参数的值,或将参数的顺序调换,返回一个新的函数对象。其语法格式为: bind(函数对象/函数指针, 参数1, 参数2, ..., 参数n)
参数中可以使用placeholders::\_1、placeholders::\_2、...(占位符),表示新函数对象的参数位置(\_1表示第一个参数,\_2表示第二个参数,以此类推)。
```c++
// 判断元素是否大于threshold
bool greaterTan(int val, int threshold)
{
return val > threshold;
}
int main()
{
vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int threshold = 5;
// 函数适配器,绑定greaterTan函数的第二个参数,生成一元谓词
// using namespace std::placeholders; // _1, _2
auto bind_fun = bind(greaterTan, placeholders::_1, threshold);
// 统计元素值大于5的个数
int ret = count_if(v.begin(), v.end(), bind_fun);
cout << ret << endl; // 4
return EXIT_SUCCESS;
};
否定适配器
not1和not2用于否定函数对象的返回值,其中: - not1:用于否定“一元函数对象”(接收1个参数,返回bool值); - not2:用于否定“二元函数对象”(接收2个参数,返回bool值)。
常与STL内置函数对象(如less、greater、equal_to、less_equal)配合使用,改变算法的判断条件。 注意:部分环境中not1可能存在兼容问题,C++11及以后更推荐使用bind适配器替代not1,代码兼容性更强、可读性更好。
vector<int> v = { 5, 12, 8, 15, 3, 20 };
// 替代not1:用bind实现“找不小于10的元素(大于等于10)”
// 等价于否定 x < 10,即 x >= 10,用bind绑定less<int>的参数顺序实现
auto it1 = find_if(v.begin(), v.end(), bind(less_equal<int>(), 10, placeholders::_1));
cout << "第一个不小于10的元素:" << *it1 << endl; // 输出12
函数指针适配器
ptr_fun用于将“普通函数指针”转化为“函数对象”,以便适配STL算法中对“函数对象”的要求(部分算法只接受函数对象,不直接接受函数指针)。C++11及以后,bind可替代ptr_fun,但了解其用法有助于理解函数适配器的核心逻辑。
// 普通函数:判断一个数是否为偶数
bool isEven(int x)
{
return x % 2 == 0;
}
int main()
{
vector<int> v = {1, 2, 3, 4, 5, 6};
// ptr_fun将isEven函数指针转化为函数对象,配合find_if找第一个偶数
auto it = find_if(v.begin(), v.end(), ptr_fun(isEven));
if (it != v.end())
{
cout << "第一个偶数:" << *it << endl; // 输出2 }
return 0;
}
使用lambda表达式
Lambda 可以直接捕获变量、绑定参数,简洁灵活。
vector<int> v = { 5, 12, 8, 15, 3, 20 };
// 统计元素大于10的个数
int threshold = 10;
int ret = count_if(v.begin(), v.end(), [threshold](int val) {return val > threshold; });
std::cout << "大于10的元素个数: " << ret << std::endl; // 3