设计模式之适配器模式(二):STL适配器

目录

1.背景

2.什么是 STL 适配器?

3.函数对象适配器

3.1.std::bind

3.2.std::not1 和 std::not2

3.3.std::mem_fn

4.容器适配器

4.1.std::stack(栈)

4.2.std::queue(队列)

4.3.std::priority_queue(优先队列)

5.迭代器适配器

5.1.std::reverse_iterator(反向迭代器)

5.2.std::back_insert_iterator(尾部插入迭代器)

6.自定义适配器

7.总结


1.背景

为什么需要适配器?

在软件设计中,适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要作用是:

  1. 将一个类的接口转换成客户希望的另外一个接口。 适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  2. 可以复用现有的类,而不需要修改它们的源代码。

  3. 允许在不改变现有代码的情况下,引入新的功能或组件。

        对于C++ STL来说,适配器提供了类似的功能。STL本身已经提供了很多强大的容器、算法和函数对象,但有时候,我们需要对这些组件进行一些定制或调整,以满足特定的需求。如果没有适配器,我们就需要手动编写大量的代码来实现这些定制功能,这会导致代码冗余、可读性差、维护困难等问题。

        以实现一个栈(Stack) 为例,场景: 需要一个栈数据结构,它支持 pushpoptopemptysize 等基本操作。

        如果不使用 STL 适配器,需要自己从头开始实现一个栈(这可能基于数组或链表来实现)。这里以 vector 为例:

#include <iostream>
#include <vector>template <typename T>
class MyStack {
private:std::vector<T> data;public:void push(const T& value) {data.push_back(value);}void pop() {if (!data.empty()) {data.pop_back();}}T& top() {return data.back();}const T& top() const {  // const 版本return data.back();}bool empty() const {return data.empty();}size_t size() const {return data.size();}
};int main() {MyStack<int> s;s.push(10);s.push(20);std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 20s.pop();std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 10return0;
}

这段代码虽然实现了栈的基本功能,但是:

  • 代码量较多:  需要手动实现所有栈的操作。

  • 重复造轮子: vector 本身已经提供了动态数组的功能,我们只是需要对 vector 的接口进行一些限制和调整。

  • 维护成本高:  如果需要修改栈的底层实现(例如,从 vector 改为 list),需要修改大量的代码。

使用 STL 适配器的情况:

#include <iostream>
#include <stack>  // 引入 stack 适配器int main() {std::stack<int> s; // 使用 std::stacks.push(10);s.push(20);std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 20s.pop();std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 10return 0;
}

使用 std::stack 适配器,可以直接利用现有的容器(默认是 std::deque,也可以指定为 std::vector 或 std::list),并将其接口适配为栈的接口。 这样,只需要一行代码就可以创建一个栈,而不需要手动实现所有栈的操作。

对比:

特性

没有适配器 (MyStack)

使用适配器 (std::stack)

代码量

较多

极少

代码复用

维护成本

可读性

稍差

更好

灵活性

稍差

更好 (可以更换底层容器)

2.什么是 STL 适配器?

设计模式之适配器模式(一):简介与使用

适配器的定义:在计算机科学中,适配器 (Adapter) 是一种设计模式,它允许将一个现有类的接口转换成另一种接口,以满足客户的期望。 适配器模式通过封装一个对象,并提供一个新的接口来访问该对象的功能。

适配器如何修改已有类的接口: 适配器模式的核心思想是 "包装"。 适配器类包含一个现有类的实例,并在其内部实现新的接口。当用户通过适配器的新接口调用方法时,适配器会将这些调用转换为对现有类实例的相应方法的调用。

适配器通常包含以下几个部分:

  • 目标接口 (Target Interface): 它定义了用户期望使用的接口,使得用户可以以一种统一的方式访问适配器的功能。目标接口的设计需要充分考虑用户的需求,使得接口简洁明了,易于使用。

  • 适配器 (Adapter): 实现了目标接口,并包含一个现有类实例。它通过封装被适配者,实现了目标接口的功能。适配器的作用是将目标接口的调用转换为对被适配者的方法调用。这种转换机制,使得适配器可以复用被适配者的功能,而无需重新编写代码。

  • 被适配者 (Adaptee):  现有类,其接口需要被适配。

适配器的工作流程如下:

  1. 客户通过目标接口调用适配器的方法。

  2. 适配器将该方法调用转换为对被适配者的方法调用。

  3. 被适配者执行相应的操作,并将结果返回给适配器。

  4. 适配器可以将结果进行转换,然后再返回给客户(可选)。

通过这种方式,适配器可以在不修改现有类的情况下,将其接口转换为客户需要的接口。

STL 中常见的适配器类型

适配器名称

功能描述

底层容器/数据结构

std::stack

将底层容器适配为栈数据结构,提供 push (入栈), pop (出栈), top (访问栈顶元素), empty (判断栈是否为空), size (返回栈的大小) 等操作。

默认是 std::deque,也可以指定为 std::vector 或 std::list

std::queue

将底层容器适配为队列数据结构,提供 push (入队), pop (出队), front (访问队首元素), back (访问队尾元素), empty (判断队列是否为空), size (返回队列的大小) 等操作。

默认是 std::deque,也可以指定为 std::list。  (注意:不能使用 std::vector 作为底层容器,因为 vector 不支持高效的头部删除操作)。

std::priority_queue

将底层容器适配为优先级队列数据结构,提供 push (插入元素), pop (删除优先级最高的元素), top (访问优先级最高的元素), empty (判断队列是否为空), size (返回队列的大小) 等操作。

默认是 std::vector,并通过堆 (heap) 数据结构来维护元素的优先级。

std::reverse_iterator

创建一个反向迭代器,用于从容器的末尾开始反向遍历容器中的元素。

任何支持双向迭代器的容器(如 std::vectorstd::liststd::dequestd::setstd::map 等)。

std::insert_iterator

创建一个插入迭代器,用于在容器中插入元素。std::back_insert_iterator 在容器尾部插入元素,std::front_insert_iterator 在容器头部插入元素 (仅适用于 std::deque 和 std::list), std::insert_iterator 在指定位置插入元素。

任何支持插入操作的容器(如 std::vectorstd::liststd::dequestd::setstd::map 等)。

std::move_iterator

创建一个移动迭代器,用于将容器中的元素移动到另一个容器中,而不是复制它们。

任何支持迭代器的容器。

std::bind

将函数或函数对象绑定到特定的参数,创建一个新的可调用对象。 这允许您创建一个具有预定义参数的函数对象,可以方便地传递给算法。

N/A (不直接关联于容器)

std::not1

std::not2

std::not1

 用于对一元谓词(接受一个参数的函数对象)的结果取反,std::not2 用于对二元谓词(接受两个参数的函数对象)的结果取反。

N/A (不直接关联于容器)

std::mem_fn

将成员函数转换为函数对象,使其可以像普通函数一样使用。 

N/A (不直接关联于容器)

特别说明:  函数对象适配器(如 std::bindstd::not1std::mem_fn)在 C++11 及以后的版本中,std::bind 的使用场景已经大大减少,因为 lambda 表达式提供了更简洁和灵活的替代方案。  std::not1 和 std::not2 也被 Lambda 表达式取代。 所以,在现代 C++ 编程中,Lambda 表达式是更推荐的选择。

3.函数对象适配器

适配器就是为算法提供接口。

3.1.std::bind

C++中的std::bind深入剖析-CSDN博客

C++20中的std::bind_front使用及原理分析-CSDN博客

std::bind 是一个函数模板,位于 <functional> 头文件中。 主要作用是:

  1. 将函数或函数对象绑定到特定的参数:  可以使用 std::bind 将函数或函数对象的一些或所有参数绑定到特定的值,创建一个新的可调用对象 (函数对象)。

  2. 推迟调用:  std::bind 创建的可调用对象不会立即执行原始函数。 只有当你调用这个新的可调用对象时,原始函数才会被执行,并且会使用之前绑定的参数。

  3. 参数重排序和占位符:  可以使用 std::bind 重新排列函数参数的顺序,或者使用占位符 (std::placeholders::_1std::placeholders::_2 等) 来表示在调用时才提供的参数。

std::bind1st 和 std::bind2nd 是 C++98 标准库提供的函数适配器,用于将二元函数对象(binary function object)转换为一元函数对象(unary function object)。 它们分别将二元函数的第一个参数或第二个参数绑定到特定的值。

  • std::bind1st(op, value):  创建一个一元函数对象,将二元函数对象 op 的第一个参数绑定到 value。  新函数对象接受一个参数,该参数会被传递给 op 作为第二个参数。

  • std::bind2nd(op, value):  创建一个一元函数对象,将二元函数对象 op 的第二个参数绑定到 value。 新函数对象接受一个参数,该参数会被传递给 op 作为第一个参数。

C++11 引入了 std::bind,它比 std::bind1st 和 std::bind2nd 更加通用和灵活,并且在 C++11 中,std::bind1st 和 std::bind2nd 已经被标记为 deprecated (不推荐使用),在C++17中,它们被正式移除。

std::bind 可以用于绑定以下类型的可调用对象:

普通函数 (Regular Functions):

#include <iostream>
#include <functional>int add(int a, int b) {return a + b;
}int main() {// 绑定 add 函数的第一个参数为 5auto add_5 = std::bind(add, 5, std::placeholders::_1);// 调用 add_5,第二个参数 (std::placeholders::_1) 在调用时提供int result = add_5(3); // 相当于调用 add(5, 3)std::cout << "Result: " << result << std::endl; // 输出:Result: 8return0;
}

成员函数 (Member Functions): 绑定成员函数时,需要提供一个指向对象实例的指针或引用,作为 std::bind 的第一个参数。

#include <iostream>
#include <functional>class MyClass {
public:int multiply(int a, int b) {return a * b;}
};int main() {MyClass obj;// 绑定 MyClass 对象的 multiply 成员函数auto multiply_by_2 = std::bind(&MyClass::multiply, &obj, 2, std::placeholders::_1); // 注意 &obj 的使用int result = multiply_by_2(5); // 相当于调用 obj.multiply(2, 5)std::cout << "Result: " << result << std::endl; // 输出:Result: 10return0;
}

函数对象 (Function Objects): 函数对象是重载了 operator() 的类。

#include <iostream>
#include <functional>class MyFunctor {
public:int operator()(int a, int b) {return a - b;}
};int main() {MyFunctor subtract;// 绑定 MyFunctor 对象的 operator()auto subtract_from_10 = std::bind(subtract, 10, std::placeholders::_1);int result = subtract_from_10(3); // 相当于调用 subtract(10, 3)std::cout << "Result: " << result << std::endl; // 输出:Result: 7return0;
}

占位符 (Placeholders):   std::placeholders::_1std::placeholders::_2std::placeholders::_3, ... 用于表示在调用 std::bind 创建的可调用对象时需要提供的参数。  _1 表示第一个参数, _2 表示第二个参数,以此类推。

#include <iostream>
#include <functional>int divide(int a, int b) {if (b == 0) {throwstd::runtime_error("Division by zero!");}return a / b;
}int main() {// 颠倒参数顺序auto divide_by = std::bind(divide, std::placeholders::_2, std::placeholders::_1);int result = divide_by(2, 10); // 相当于调用 divide(10, 2)std::cout << "Result: " << result << std::endl; // 输出:Result: 5return0;
}

std::bind 是一个强大的适配器,可以用于创建灵活的可调用对象。 它可以绑定函数、成员函数和函数对象,并允许预先设置一些参数,或重新排列参数的顺序。  但是,在 C++11 及以后的版本中,Lambda 表达式提供了更简洁和灵活的替代方案,因此在很多情况下,使用 Lambda 表达式可能更为方便。 尤其是在简单的情况下,Lambda 表达式更加易读。

应用场景示例:假设要遍历一个数组,并对每个元素加一个值,首先考虑到for_each算法,但是for_each只有三个参数,如何把要加的值传入呢?

  1. 使用std::bind绑定参数,把多个参数绑成一个。

  2. 使用const修饰operator()成员函数。

就像一个笔记本只有一个 USB 接口,但是想插入四个 USB 口怎么办呢?很简单,用一个扩展口,扩展出四个 USB 口,适配器就类似扩展器的道理。

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>class PrintInt{
public:// 2. 使用`const`修饰`operator()`成员函数。void operator()(int tmp, int value) const {std::cout << tmp + value << " ";}
};int main() 
{std::vector<int> v;for (int i = 0; i < 10; ++i) v.emplace_back(i + 1);// 1. 使用`std::bind`绑定参数,把多个参数绑成一个。for_each(v.begin(), v.end(), std::bind(PrintInt(), 100, std::placeholders::_1));std::cout << std::endl;return0;
}

3.2.std::not1 和 std::not2

C++17之std::not_fn的使用和实现原理-CSDN博客

std::not1 和 std::not2 用于对函数对象的结果进行逻辑取反。

  • std::not1:  接受一个一元谓词 (Unary Predicate) 作为参数,并返回一个新的函数对象,该函数对象返回原始谓词结果的逻辑非。  一元谓词指的是接受一个参数并返回 bool 值的函数或函数对象。

  • std::not2:  接受一个二元谓词 (Binary Predicate) 作为参数,并返回一个新的函数对象,该函数对象返回原始谓词结果的逻辑非。  二元谓词指的是接受两个参数并返回 bool 值的函数或函数对象。

注意: std::not1 和 std::not2 在 C++17 中已被弃用 (deprecated),并在 C++20 中被移除。  这是因为 Lambda 表达式提供了更简洁和灵活的替代方案。但是,了解它们的工作原理仍然有助于理解函数对象和适配器的概念。

示例 1: 使用 std::not1

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>// 一元谓词:判断数字是否为偶数
bool isEven(int num) 
{return num % 2 == 0;
}int main() 
{std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 使用 std::not1 取反 isEven 谓词std::vector<int> odd_numbers;std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(odd_numbers), std::not1(std::ptr_fun(isEven))); // 注意 ptr_fun 的使用std::cout << "Odd numbers: ";for (int num : odd_numbers) {std::cout << num << " ";}std::cout << std::endl; // 输出:Odd numbers: 1 3 5 7 9return0;
}

这里使用了 std::ptr_fun,这是因为 std::not1 要求参数是一个 适应性函数对象, std::ptr_fun 用于将普通函数转换为适应性函数对象。

示例 2: 使用 std::not2 (虽然不常用,但可以了解概念)

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>// 二元谓词:判断两个数字是否相等
bool isEqual(int a, int b) 
{return a == b;
}int main() 
{std::vector<int> numbers1 = {1, 2, 3, 4, 5};std::vector<int> numbers2 = {5, 4, 3, 2, 1};// 检查 numbers1 和 numbers2 在相同位置上的元素是否都相等// 这里只是为了演示 std::not2 的用法,实际场景中很少这样使用。bool all_equal = true;for (size_t i = 0; i < numbers1.size(); ++i) {if (std::not2(std::ptr_fun(isEqual))(numbers1[i], numbers2[i])) { // 使用 std::not2all_equal = false;break;}}if (all_equal) {std::cout << "All elements at corresponding positions are equal." << std::endl;} else {std::cout << "Not all elements at corresponding positions are equal." << std::endl; // 输出}return0;
}

由于 std::not1 和 std::not2 已经过时,建议使用 Lambda 表达式来实现相同的功能。了解 std::not1 和 std::not2 的目的是理解函数对象和适配器的概念,但在实际开发中避免使用它们。

3.3.std::mem_fn

std::mem_fn 的作用:

  • 将成员函数转换为函数对象:  它接受一个成员函数指针作为参数,并返回一个函数对象。  这个函数对象可以像普通函数一样被调用,并且它会将调用转发到指定的对象实例的成员函数。

  • std::mem_fn 创建的函数对象可以方便地与标准库中的算法 (如 std::transformstd::for_each) 一起使用,以对容器中的对象调用成员函数。

使用 std::mem_fn 调用对象的成员函数:

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>class MyClass {
public:MyClass(int value) : value_(value) {}int getValue() const {return value_;}void printValue() const {std::cout << "Value: " << value_ << std::endl;}private:int value_;
};int main() {std::vector<MyClass> objects;objects.emplace_back(10);objects.emplace_back(20);objects.emplace_back(30);// 1. 使用 std::mem_fn 调用 getValue() 成员函数并存储结果std::vector<int> values;std::transform(objects.begin(), objects.end(), std::back_inserter(values), std::mem_fn(&MyClass::getValue));std::cout << "Values: ";for (int value : values) {std::cout << value << " ";}std::cout << std::endl; // 输出:Values: 10 20 30// 2. 使用 std::mem_fn 调用 printValue() 成员函数std::cout << "Calling printValue():" << std::endl;std::for_each(objects.begin(), objects.end(), std::mem_fn(&MyClass::printValue));// 输出:// Calling printValue():// Value: 10// Value: 20// Value: 30// 3. 使用 std::mem_fn 和 std::bind 结合// 创建一个函数对象,将每个 MyClass 对象的 value_ 属性增加 5auto incrementValue = std::bind([](MyClass& obj, int increment) {// 注意:需要访问 MyClass 的私有成员,这里假设可以通过友元函数或者添加公共的修改方法来实现。}, std::placeholders::_1, 5);//std::for_each(objects.begin(), objects.end(), incrementValue);return0;
}

注意:

  • 在调用成员函数时,需要提供对象实例。std::mem_fn 创建的函数对象会自动处理这个问题。

  • std::mem_fn 返回的函数对象可以像普通函数一样被调用。

std::mem_fn 在某些旧代码中可能会遇到,理解它的作用有助于阅读和维护这些代码。

4.容器适配器

C++ 的容器适配器是标准模板库(STL)中的一种特殊类型的容器,它们基于基本的序列容器(如 vectordeque 和 list)实现,用于适配特定的需求和场景。容器适配器并不直接提供完整的序列容器接口,而是通过限制接口或添加特定功能来简化操作。

C++ 提供了三种主要的容器适配器:

容器

数据结构

操作限制

默认底层容器

stack

仅栈顶操作(LIFO)

deque
queue

队列

仅队首、队尾操作(FIFO)

deque
priority_queue

优先队列

按优先级操作

vector

4.1.std::stack(栈)

栈遵循后进先出(LIFO)的原则。默认情况下,它基于std::deque实现,不过也可以使用std::vector或者std::list作为底层容器。以下是示例代码:

#include <iostream>
#include <stack>int main() {std::stack<int> myStack;myStack.push(1);myStack.push(2);myStack.push(3);while (!myStack.empty()) {std::cout << myStack.top() << " ";myStack.pop();}std::cout << std::endl;return 0;
}

在上述代码中,先创建了一个整数类型的栈myStack,接着使用push方法把元素压入栈,再用top方法获取栈顶元素,最后用pop方法将栈顶元素弹出。

4.2.std::queue(队列)

队列遵循先进先出(FIFO)的原则。默认基于std::deque实现,也能使用std::list作为底层容器。示例如下:

#include <iostream>
#include <queue>int main() {std::queue<int> myQueue;myQueue.push(1);myQueue.push(2);myQueue.push(3);while (!myQueue.empty()) {std::cout << myQueue.front() << " ";myQueue.pop();}std::cout << std::endl;return 0;
}

此代码创建了一个整数类型的队列myQueue,使用push方法将元素加入队列,用front方法获取队首元素,再用pop方法移除队首元素。

4.3.std::priority_queue(优先队列)

优先队列中的元素按照优先级排序,优先级高的元素先出队。默认基于std::vector实现,使用std::less作为比较函数。示例如下:

#include <iostream>
#include <queue>int main() {std::priority_queue<int> myPriorityQueue;myPriorityQueue.push(3);myPriorityQueue.push(1);myPriorityQueue.push(2);while (!myPriorityQueue.empty()) {std::cout << myPriorityQueue.top() << " ";myPriorityQueue.pop();}std::cout << std::endl;return 0;
}

在这个例子里,创建了一个整数类型的优先队列myPriorityQueue,使用push方法插入元素,top方法获取优先级最高的元素,pop方法移除该元素。

5.迭代器适配器

        迭代器适配器用于对迭代器进行包装或转换,以改变其行为或功能。迭代器适配器可以在不修改原始容器或算法的情况下,灵活地控制和操作迭代器。

        C++ 提供了以下几种主要的迭代器适配器:

迭代器适配器

功能

常用方法或函数

插入迭代器

将元素插入到容器的指定位置

std::back_inserter

 ,std::front_inserterstd::inserter

流迭代器

从输入流读取或向输出流写入数据

std::istream_iterator

std::ostream_iterator

反向迭代器

反向遍历容器

std::reverse_iterator

rbegin() / rend()

移动迭代器

将元素从一个容器移动到另一个容器,避免拷贝

std::make_move_iterator

5.1.std::reverse_iterator(反向迭代器)

反向迭代器能够让你以相反的顺序遍历容器。示例如下:

#include <iostream>
#include <vector>
#include <iterator>int main() {std::vector<int> myVector = {1, 2, 3, 4, 5};for (auto it = myVector.rbegin(); it != myVector.rend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}

上述代码创建了一个整数类型的向量myVector,使用rbegin()rend()方法获取反向迭代器,以反向顺序遍历向量。

5.2.std::back_insert_iterator(尾部插入迭代器)

尾部插入迭代器可用于在容器尾部插入元素。示例如下:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>int main() {std::vector<int> myVector;std::back_insert_iterator<std::vector<int>> backInserter(myVector);*backInserter = 1;++backInserter;*backInserter = 2;++backInserter;*backInserter = 3;for (int num : myVector) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

此代码创建了一个整数类型的向量myVector,并使用std::back_insert_iterator在向量尾部插入元素。

6.自定义适配器

C++实现自定义对象支持Range-based循环语法_std::set内部元素只读-CSDN博客

在 C++ 中编写自定义迭代器适配器需要遵循迭代器的规范,同时利用模板和运算符重载实现功能。比如,自定义实现一个能将迭代器步长调整为 N 的适配器。

迭代器适配器的核心要求:

  1. 满足迭代器类别(如输入、前向、双向、随机访问迭代器)

  2. 实现必要的运算符重载++*==!=, 等)

  3. 提供标准类型别名value_typedifference_typeiterator_category 等)

定义适配器类模板:

#include <iterator>
#include <cstddef>template <typename Iterator, int N>
class StrideIterator {
public:// 标准类型别名(必须)using value_type = typenamestd::iterator_traits<Iterator>::value_type;using difference_type = typenamestd::iterator_traits<Iterator>::difference_type;using iterator_category = typenamestd::iterator_traits<Iterator>::iterator_category;using pointer = typenamestd::iterator_traits<Iterator>::pointer;using reference = typenamestd::iterator_traits<Iterator>::reference;private:Iterator m_current;Iterator m_end;public:// 构造函数StrideIterator(Iterator begin, Iterator end) : m_current(begin), m_end(end) {}// 前置递增运算符(核心逻辑)StrideIterator& operator++() {if (std::distance(m_current, m_end) >= N) {std::advance(m_current, N);} else {m_current = m_end;}return *this;}// 解引用运算符reference operator*() const { if (m_current == m_end) throwstd::out_of_range("Dereferencing end iterator");return *m_current; }// 相等性比较booloperator==(const StrideIterator& other) const {return m_current == other.m_current;}booloperator!=(const StrideIterator& other) const {return !(*this == other);}// 随机访问迭代器特有操作StrideIterator& operator+=(difference_type n) {m_current += n * N;  // 步长放大 N 倍if (m_current > m_end) m_current = m_end;return *this;}StrideIterator operator+(difference_type n) const {auto tmp = *this;tmp += n;return tmp;}// 下标访问(仅随机访问)reference operator[](difference_type n) const {return m_current[n * N];}// 逆向迭代支持(双向迭代器)StrideIterator& operator--() {if (m_current != m_end) {std::advance(m_current, -N);}return *this;}// 有效性检查接口explicit operator bool() const { return m_current != m_end; }
};

创建辅助函数(类似 std::make_pair):

template <int N, typename Iterator>
StrideIterator<Iterator, N> make_stride_iterator(Iterator begin, Iterator end) {return StrideIterator<Iterator, N>(begin, end);
}

使用:

#include <vector>
#include <algorithm>
#include <iostream>int main() {std::vector<int> data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};// 创建步长为3的迭代器auto begin = make_stride_iterator<3>(data.begin(), data.end());auto end = make_stride_iterator<3>(data.end(), data.end());// 使用标准算法遍历std::for_each(begin, end, [](int n) {std::cout << n << " ";});return  0;
}

7.总结

        在软件开发的世界里,代码的复用性、可维护性和扩展性是衡量一个项目成功与否的关键因素。而适配器模式(Adapter Pattern)作为一种经典的设计模式,正是为了解决这些问题而诞生的。C++ STL(Standard Template Library)适配器则是将这种设计模式完美融入现代编程语言的一个典范。它以极简的代码量实现了强大的功能,为开发者提供了极大的便利。

        STL适配器作为C++标准库中接口转换的核心工具,其核心价值体现在三个方面:

  • 接口转换(如容器适配器将序列容器转化为栈/队列结构)。

  • 功能扩展(迭代器适配器实现反向遍历或流式操作)。

  • 逻辑封装(函数对象适配器参数绑定与成员函数调用)。

推荐阅读:

设计模式之适配器模式(一):简介与使用

C++中的std::bind深入剖析

C++20中的std::bind_front使用及原理分析

C++17之std::not_fn的使用和实现原理

C++实现自定义对象支持Range-based循环语法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/76448.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW故障诊断数据处理方法

在LabVIEW故障诊断系统中&#xff0c;数据处理直接决定诊断的准确性和效率。工业现场常面临噪声干扰、数据量大、实时性要求高等挑战&#xff0c;需针对性地选择处理方法。本文结合电机故障诊断、轴承损伤检测等典型案例&#xff0c;详解数据预处理、特征提取、模式识别三大核心…

51单片机的五类指令(二)——算术运算类指令

目录 一、加法指令 &#xff08;一&#xff09;不带进位加法指令&#xff08;ADD&#xff09; &#xff08;二&#xff09;带进位加法指令&#xff08;ADDC&#xff09; &#xff08;三&#xff09;加 1 指令&#xff08;INC&#xff09; &#xff08;四&#xff09;十进制…

【FPGA】状态机思想回顾流水灯

【FPGA】状态机思想回顾流水灯 一、LED流水灯实现1. 基本要求2. 状态机思想3. 关键代码4. 仿真测试5. 效果演示 二、CPLD和FPGA1. 技术区别2. 应用场景 三、HDLbits组合逻辑题目四、实验总结 一、LED流水灯实现 1. 基本要求 用状态机思想写一个 LED流水灯的FPGA代码写出仿真测…

Python网络爬虫:从入门到实践

目录 什么是网络爬虫&#xff1f; 网络爬虫的工作原理 常用Python爬虫库 编写爬虫的步骤 实战示例 注意事项与道德规范 未来趋势 1. 什么是网络爬虫&#xff1f; 网络爬虫&#xff08;Web Crawler&#xff09;是一种自动化程序&#xff0c;通过模拟人类浏览行为&#x…

3D意识(3D Awareness)浅析

一、简介 3D意识&#xff08;3D Awareness&#xff09;主要是指视觉基础模型&#xff08;visual foundation models&#xff09;对于3D结构的意识或感知能力&#xff0c;即这些模型在处理2D图像时是否能够理解和表示出图像中物体或场景的3D结构&#xff0c;其具体体现在编码场景…

红包-算法

function resPackage(money,num){// 总金额 目前剩余总金额let sum money, currentsum moneylet res [];// 最大值for(let i 0;i<num-1;i){let n parseFloat((Math.random()*currentsum).toFixed(2)) //0-10的随机数if(n<0.1) n 0.1;if(n>sum…

最小二乘求解器lstsq,处理带权重和L2正则的线性回归

目录 代码注释版&#xff1a; 关键功能说明&#xff1a; torch.linalg.cholesky 的原理 代码示例 Cholesky 分解的应用 与 torch.cholesky 的区别 总结 代码注释版&#xff1a; from typing import Optionalimport torchdef lstsq(matrix: torch.Tensor, rhs: torch.Te…

AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用

目前&#xff0c;流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型&#xff0c;能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…

DHT11数字温湿度传感器驱动开发全解析(下) | 零基础入门STM32第八十八步

主题内容教学目的/扩展视频DHT11芯片电路连接&#xff0c;手册分析。驱动程序&#xff0c;读出数据。能读出温湿度值即可。 师从洋桃电子&#xff0c;杜洋老师 &#x1f4d1;文章目录 一、硬件接口与通信原理1.1 硬件连接拓扑1.2 单总线通信时序 二、驱动代码深度解析&#xff…

24、网络编程基础概念

网络编程基础概念 网络结构模式MAC地址IP地址子网掩码端口网络模型协议网络通信的过程&#xff08;封装与解封装&#xff09; 网络结构模式 C/S结构&#xff0c;由客户机和服务器两部分组成&#xff0c;如QQ、英雄联盟 B/S结构&#xff0c;通过浏览器与服务器进程交互&#xf…

【超详细】讲解Ubuntu上如何配置分区方案

Ubuntu 的分区方案 一、通用分区方案&#xff08;200G为例&#xff09; EFI系统分区&#xff08;仅UEFI启动模式需要&#xff0c;&#xff09; 大小&#xff1a;512MB–1GB类型&#xff1a;主分区&#xff08;FAT32格式&#xff09;挂载点&#xff1a;/boot/efi说明&#xff1…

函数的局部变量和全局变量的区分,Kimi的回答

这段代码的目的是通过计算 2**i 和 5**i 的首位数字&#xff0c;并将这两个首位数字的乘积添加到一个集合中&#xff0c;最终返回这些乘积的总和。下面是具体的解释和问题的分析。 sum_t的角色&#xff1a; sum_t 是一个累加器&#xff0c;用来存储所有独特的&#xff08;不重复…

RNN模型及NLP应用(5/9)——多层RNN、双向RNN、预训练

声明&#xff1a; 本文基于哔站博主【Shusenwang】的视频课程【RNN模型及NLP应用】&#xff0c;结合自身的理解所作&#xff0c;旨在帮助大家了解学习NLP自然语言处理基础知识。配合着视频课程学习效果更佳。 材料来源&#xff1a;【Shusenwang】的视频课程【RNN模型及NLP应用…

【3.软件工程】3.4 原型及相关模型

软件开发模型进化论&#xff1a;从原型驱动到混合模型的完整指南 &#x1f504; 一、模型进化关系全景图 #mermaid-svg-GcOFjt54gUs4oPeu {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GcOFjt54gUs4oPeu .error-i…

硬件与软件的边界-从单片机到linux的问答详解

硬件与软件的边界——从单片机到 Linux 设备驱动的问答详解 在嵌入式开发和操作系统领域&#xff0c;经常会有人问&#xff1a; “如果一个设备里没有任何代码&#xff0c;硬件是不是依然会工作&#xff1f;例如&#xff0c;数据收发、寄存器数据存储、甚至中断触发&#xff…

玛卡巴卡的k8s知识点问答题(七)

25. 说明 Job 与 CronJob 的功能 Job 功能&#xff1a; 用于运行一次性任务&#xff08;批处理任务&#xff09;&#xff0c;确保一个或多个 Pod 成功完成任务后退出。 适用于数据处理、备份、测试等场景&#xff0c;任务完成后 Pod 不会自动重启。 特点&#xff1a; 任务…

【NLP 51、一些LLM模型结构上的变化】

目录 一、multi-head 共享 二、attention结构 1.传统的Tranformer结构 2.GPTJ —— 平行放置的Transformer结构 三、归一化层位置的选择 1.Post LN&#xff1a; 2.Pre-LN【目前主流】&#xff1a; 3.Sandwich-LN&#xff1a; 四、归一化函数选择 1.传统的归一化函数 LayerNorm …

VS+Qt配置QtXlsx库实现execl文件导入导出(全教程)

一、配置QtXlsx 1.1 下载解压QtXlsxWriter&#xff08;在github下载即可&#xff09; 网址&#xff1a;https://github.com/dbzhang800/QtXlsxWriter 1.2 使用qt运行 点击qtxlsx.pro运行QtXlsxWriter 选择DesktopQt51211MSVC201564bit编译器&#xff08;选择自己本地电脑qt…

Golang的文件处理优化策略

Golang的文件处理优化策略 一、Golang的文件处理优化策略概述 是一门效率高、易于编程的编程语言&#xff0c;它的文件处理能力也非常强大。 在实际开发中&#xff0c;需要注意一些优化策略&#xff0c;以提高文件处理的效率和性能。 本文将介绍Golang中的文件处理优化策略&…

自学-C语言-基础-数组、函数、指针、结构体和共同体、文件

这里写自定义目录标题 代码环境&#xff1a;&#xff1f;问题思考&#xff1a;一、数组二、函数三、指针四、结构体和共同体五、文件问题答案&#xff1a; 代码环境&#xff1a; Dev C &#xff1f;问题思考&#xff1a; 把上门的字母与下面相同的字母相连&#xff0c;线不能…