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

目录

1.背景

[2.什么是 STL 适配器?](#2.什么是 STL 适配器?)

3.函数对象适配器

3.1.std::bind

[3.2.std::not1 和 std::not2](#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) 为例,场景: 需要一个栈数据结构,它支持 push, pop, top, empty, size 等基本操作。

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

cpp 复制代码
#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 适配器的情况:

cpp 复制代码
#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::vectorstd::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::vectorstd::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::vector, std::list, std::deque, std::set, std::map 等)。
std::insert_iterator 创建一个插入迭代器,用于在容器中插入元素。std::back_insert_iterator 在容器尾部插入元素,std::front_insert_iterator 在容器头部插入元素 (仅适用于 std::dequestd::list), std::insert_iterator 在指定位置插入元素。 任何支持插入操作的容器(如 std::vector, std::list, std::deque, std::set, std::map 等)。
std::move_iterator 创建一个移动迭代器,用于将容器中的元素移动到另一个容器中,而不是复制它们。 任何支持迭代器的容器。
std::bind 将函数或函数对象绑定到特定的参数,创建一个新的可调用对象。 这允许您创建一个具有预定义参数的函数对象,可以方便地传递给算法。 N/A (不直接关联于容器)
std::not1 , std::not2 std::not1 用于对一元谓词(接受一个参数的函数对象)的结果取反,std::not2 用于对二元谓词(接受两个参数的函数对象)的结果取反。 N/A (不直接关联于容器)
std::mem_fn 将成员函数转换为函数对象,使其可以像普通函数一样使用。 N/A (不直接关联于容器)

特别说明: 函数对象适配器(如 std::bind, std::not1, std::mem_fn)在 C++11 及以后的版本中,std::bind 的使用场景已经大大减少,因为 lambda 表达式提供了更简洁和灵活的替代方案。 std::not1std::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::_1, std::placeholders::_2 等) 来表示在调用时才提供的参数。

std::bind1ststd::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::bind1ststd::bind2nd 更加通用和灵活,并且在 C++11 中,std::bind1ststd::bind2nd 已经被标记为 deprecated (不推荐使用),在C++17中,它们被正式移除。

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

普通函数 (Regular Functions):

cpp 复制代码
#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 的第一个参数。

cpp 复制代码
#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() 的类。

cpp 复制代码
#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::_1, std::placeholders::_2, std::placeholders::_3, ... 用于表示在调用 std::bind 创建的可调用对象时需要提供的参数。 _1 表示第一个参数, _2 表示第二个参数,以此类推。

cpp 复制代码
#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 口,适配器就类似扩展器的道理。

cpp 复制代码
#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::not1std::not2 用于对函数对象的结果进行逻辑取反。

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

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

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

示例 1: 使用 std::not1

cpp 复制代码
#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 (虽然不常用,但可以了解概念)

cpp 复制代码
#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::not1std::not2 已经过时,建议使用 Lambda 表达式来实现相同的功能。了解 std::not1std::not2 的目的是理解函数对象和适配器的概念,但在实际开发中避免使用它们。

3.3.std::mem_fn

std::mem_fn 的作用:

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

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

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

cpp 复制代码
#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)中的一种特殊类型的容器,它们基于基本的序列容器(如 vectordequelist)实现,用于适配特定的需求和场景。容器适配器并不直接提供完整的序列容器接口,而是通过限制接口或添加特定功能来简化操作。

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

容器 数据结构 操作限制 默认底层容器
stack 仅栈顶操作(LIFO) deque
queue 队列 仅队首、队尾操作(FIFO) deque
priority_queue 优先队列 按优先级操作 vector

4.1.std::stack(栈)

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

cpp 复制代码
#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作为底层容器。示例如下:

cpp 复制代码
#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作为比较函数。示例如下:

cpp 复制代码
#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_inserterstd::front_inserterstd::inserter
流迭代器 从输入流读取或向输出流写入数据 std::istream_iteratorstd::ostream_iterator
反向迭代器 反向遍历容器 std::reverse_iteratorrbegin() / rend()
移动迭代器 将元素从一个容器移动到另一个容器,避免拷贝 std::make_move_iterator

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

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

cpp 复制代码
#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(尾部插入迭代器)

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

cpp 复制代码
#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_type, difference_type, iterator_category 等)

定义适配器类模板:

cpp 复制代码
#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):

cpp 复制代码
template <int N, typename Iterator>
StrideIterator<Iterator, N> make_stride_iterator(Iterator begin, Iterator end) {
    return StrideIterator<Iterator, N>(begin, end);
}

使用:

cpp 复制代码
#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循环语法

相关推荐
二进制人工智能2 小时前
【QT5 网络编程示例】TCP 通信
网络·c++·qt·tcp/ip
莫有杯子的龙潭峡谷4 小时前
3.31 代码随想录第三十一天打卡
c++·算法
AaronZZH5 小时前
【进阶】vscode 中使用 cmake 编译调试 C++ 工程
c++·ide·vscode
杨筱毅5 小时前
【性能优化点滴】odygrd/quill 中将 MacroMetadata 变量声明为 constexpr
c++·性能优化
NaZiMeKiY5 小时前
C++ 结构体与函数
开发语言·c++
涛ing5 小时前
【Git “fetch“ 命令详解】
linux·c语言·c++·人工智能·git·vscode·svn
学习是种信仰啊6 小时前
QT图片轮播器实现方法二(QT实操2)
开发语言·c++·qt
nqqcat~7 小时前
STL常用算法
开发语言·c++·算法
_GR7 小时前
2022年蓝桥杯第十三届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·蓝桥杯·动态规划
虾球xz8 小时前
游戏引擎学习第195天
c++·学习·游戏引擎