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

news2025/4/1 23:38:10

目录

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: 20
    s.pop();
    std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 10
    return0;
}

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

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

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

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

使用 STL 适配器的情况:

#include <iostream>
#include <stack>  // 引入 stack 适配器

int main() {
    std::stack<int> s; // 使用 std::stack
    s.push(10);
    s.push(20);
    std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 20
    s.pop();
    std::cout << "Top: " << s.top() << std::endl;  // Output: Top: 10
    return 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 函数的第一个参数为 5
   auto 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: 8

   return0;
}

成员函数 (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: 10

   return0;
}

函数对象 (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: 7

   return0;
}

占位符 (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: 5

    return0;
}

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 9

    return0;
}

这里使用了 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::not2
            all_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_ 属性增加 5
    auto 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.coloradmin.cn/o/2325002.html

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

相关文章

【区块链安全 | 第十六篇】类型之值类型(三)

文章目录 函数类型声明语法转换成员合约更新时的值稳定性示例 函数类型 函数类型是函数的类型。函数类型的变量可以通过函数进行赋值&#xff0c;函数类型的参数可以用来传递函数并返回函数。 函数类型有两种类型&#xff1a;内部函数和外部函数。 内部函数只能在当前合约内调…

Kubernetes对象基础操作

基础操作 文章目录 基础操作一、创建Kubernetes对象1.使用指令式命令创建Deployment2.使用指令式对象配置创建Deployment3.使用声明式对象配置创建Deployment 二、操作对象的标签1.为对象添加标签2.修改对象的标签3.删除对象标签4.操作具有指定标签的对象 三、操作名称空间四、…

Java与代码审计-Java基础语法

Java基础语法 package com.woniuxy.basic;public class HelloWorld {//入口函数public static void main(String[] args){System.out.println("Hello World");for(int i0;i< args.length;i){System.out.println(args[i]);}} }运行结果如下: 但是下面那个没有参数…

Xenium | 细胞邻域(Cellular Neighborhood)分析(fixed radius)

上节我们介绍了空间转录组数据分析中常见的细胞邻域分析&#xff0c;CN计算过程中定义是否为细胞邻居的方法有两种&#xff0c;一种是上节我们使用固定K最近邻方法(fixed k-nearest neighbors)定义细胞Neighborhood&#xff0c;今天我们介绍另外一种固定半径范围内(fixed radiu…

Python:爬虫概念与分类

网络请求&#xff1a; https://www.baidu.com url——统一资源定位符 请求过程&#xff1a; 客户端&#xff0c;指web浏览器向服务器发送请求 请求&#xff1a;请求网址(request url)&#xff1b;请求方法(request methods)&#xff1b;请求头(request header)&…

SQLMesh调度系统深度解析:内置调度与Airflow集成实践

本文系统解析SQLMesh的两种核心调度方案&#xff1a;内置调度器与Apache Airflow集成。通过对比两者的适用场景、架构设计和操作流程&#xff0c;为企业构建可靠的数据分析流水线提供技术参考。重点内容包括&#xff1a; 内置调度器的轻量级部署与性能优化策略Airflow集成的端到…

Multism TL494仿真异常

仿真模型如下&#xff1a;开关频率少了一半&#xff0c;而且带不动负载&#xff0c;有兄弟知道为什么吗 这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码…

HarmonyOS NEXT开发进阶(十五):日志打印 hilog 与 console.log 的区别

文章目录 一、前言二、两者区别对比三、HiLog 详解四、拓展阅读 一、前言 在日常开发阶段&#xff0c;日志打印是调试程序非常常用的操作&#xff0c;在鸿蒙的官方文档中介绍了hilog这种方式&#xff0c;前端转过来的开发者发现console.log也可以进行日志打印&#xff0c;而且…

vue 权限应用

目录 一、系统菜单栏权限 二、系统页面按钮权限 在企业开发中&#xff0c;不同的用户所扮演的角色不一样&#xff0c;角色拥有权限&#xff0c;所以用户拥有角色&#xff0c;就会有角色对应的权限。例如&#xff0c;张三是系统管理员角色&#xff0c;登录后就拥有整个系统的…

鸿蒙HarmonyOS NEXT设备升级应用数据迁移流程

数据迁移是什么 什么是数据迁移&#xff0c;对用户来讲就是本地数据的迁移&#xff0c;终端设备从HarmonyOS 3.1 Release API 9及之前版本&#xff08;单框架&#xff09;迁移到HarmonyOS NEXT&#xff08;双框架&#xff09;后保证本地数据不丢失。例如&#xff0c;我在某APP…

利用 PCI-Express 交换机实现面向未来的推理服务器

在数据中心系统的历史上&#xff0c;没有比被 Nvidia 选为其 AI 系统的组件供应商更高的赞誉了。 这就是为什么新兴的互连芯片制造商 Astera Labs 感到十分高兴&#xff0c;因为该公司正在 PCI-Express 交换机、PCI-Express 重定时器和 CXL 内存控制器方面与 Broadcom 和 Marv…

Python if else while for 学习笔记

一.if&#xff0c;else if语句用于根据条件执行代码块 else语句可与if语句结合&#xff0c;当if判断为假时执行else语句 x10 if x>5:print("x大于5") y3 if y>5:print("y大于5") else:print("y小于等于5")结果&#xff1a; 二.while循环…

正则化是什么?

正则化&#xff08;Regularization&#xff09;是机器学习中用于防止模型过拟合&#xff08;Overfitting&#xff09;的一种技术&#xff0c;通过在模型训练过程中引入额外的约束或惩罚项&#xff0c;降低模型的复杂度&#xff0c;从而提高其泛化能力&#xff08;即在未见数据上…

搜索-BFS

马上蓝桥杯了&#xff0c;最近刷了广搜&#xff0c;感觉挺有意思的&#xff0c;广搜题类型都差不多&#xff0c;模板也一样&#xff0c;大家写的时候可以直接套模板 这里给大家讲一个比较经典的广搜题-迷宫 题目问问能否走到 (n,m) 位置&#xff0c;假设最后一个点是我们的&…

《边缘计算风云录:FPGA与MCU的算力之争》

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 88万阅读 1.6万收藏 文章目录 **第一章&#xff1a;边城烽烟——数据洪流压境****第二章&#xff1a;寒铁剑匣——FPGA的千机变****第三章&#xff1a;枯木禅杖——MCU的至简道****第四章&#xff1a;双生契…

R-GCN-Modeling Relational Data with GraphConvolutional Networks(论文笔记)

CCF等级&#xff1a;B 发布时间&#xff1a;2018年6月 25年3月31日交 目录 一、简介 二、原理 1.整体 2.信息交换与更新 2.1基分解 2.2块对角矩阵 3.实体分类或链接预测 3.1实体分类 3.2链接预测 三、结论和未来工作 一、简介 RGCN通过允许不同关系类型之间的信息…

【C++初阶】----模板初阶

1.泛型函数 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。 2.函数模板 2.1函数模板的概念 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型…

Pycharm(七):几个简单案例

一.剪刀石头布 需求&#xff1a;和电脑玩剪刀石头布游戏 考察点&#xff1a;1.随机数&#xff1b;2.判断语句 import random # numrandom.randint(1,3) # print(num) # print(**30) #1.录入玩家手势 playerint(input(请输入手势&#xff1a;&#xff08;1.剪刀 2.石头 3&…

gnvm切换node版本号

1. gnvm下载官网 GNVM - Node.js version manager on Windows by Go 2. 安装 2.1 不存在 Node.js 环境 下载并解压缩 gnvm.exe 保存到任意文件夹&#xff0c;并将此文件夹加入到环境变量 Path。 2.2 存在 Node.js 环境 下载并解压缩 gnvm.exe 保存到 Node.js 所在的文件夹。 2.…

PyTorch 深度学习实战(29):目标检测与 YOLOv12 实战

在上一篇文章中&#xff0c;我们探讨了对比学习与自监督表示学习。本文将深入计算机视觉的核心任务之一——目标检测&#xff0c;重点介绍最新的 YOLOv12 (You Only Look Once v12) 算法。我们将使用 PyTorch 实现 YOLOv12 模型&#xff0c;并在 COCO 数据集上进行训练和评估。…