第16课-C++ STL 学习之【双向迭代器】(正,反向迭代器)

一、前言

在 C++ 的标准模板库(STL)中,迭代器是一种非常重要的概念,它提供了一种统一的方式来访问容器中的元素。在上一篇文章中我们学习了反向迭代器,它允许我们反向遍历容器。除了反向迭代器外,还有双向迭代器,它结合了正向迭代器和反向迭代器的一些特性,能够在容器中进行双向遍历。本文将详细介绍双向迭代器的设计思想、实现方式以及在常见容器中的应用。

二、双向迭代器设计

(一)双向思想

双向迭代器的核心思想是能够在容器中进行双向移动。与正向迭代器只能向前移动(通过operator++)和反向迭代器只能向后移动(通过operator--)不同,双向迭代器既可以向前移动,也可以向后移动。这使得它在某些需要在容器中来回遍历的场景下非常实用。

例如 ,对于一个存储整数的vector容器{1, 2, 3, 4, 5},使用正向迭代器遍历得到的结果是1 2 3 4 5,使用反向迭代器遍历得到的结果是5 4 3 2 1,而使用双向迭代器则可以根据需要灵活地在两个方向上进行遍历。

(二)多参数模板

在设计双向迭代器类时,为了处理不同类型的容器和元素,我们可以借鉴反向迭代器中多参数模板的思想。通过使用多参数模板,可以使双向迭代器类更加通用,能够适应各种不同的容器和元素类型。

例如,我们可以定义一个双向迭代器模板类如下:

template <class Iterator, class Ref, class Ptr>
struct __bidirectional_iterator
{
    typedef __bidirectional_iterator<Iterator, Ref, Ptr> self;
    Iterator _cur;

    __bidirectional_iterator(Iterator cur) : _cur(cur) {}
};

这里的Iterator表示底层的正向迭代器类型,Ref用于返回目标对象引用,Ptr用于返回目标对象指针。

(三)基本操作实现

向前移动操作(operator++

双向迭代器的向前移动操作与正向迭代器类似,但需要考虑到它可能是基于其他迭代器类型实现的。例如,如果是基于正向迭代器实现的双向迭代器,operator++操作可以直接调用底层正向迭代器的operator++操作。

self& operator++()
{
    ++_cur;
    return *this;
}
self operator++(int)
{
    Iterator tmp = _cur;
    ++_cur;
    return tmp;
}

向后移动操作(operator--
向后移动操作是双向迭代器的关键特性之一。它的实现方式与反向迭代器的operator--操作类似,即与正向迭代器的移动方向相反。

self& operator--()
{
    --_cur;
    return *this;
}
self operator--(int)
{
    Iterator tmp = _cur;
    --_cur;
    return tmp;
}

解引用操作(operator*
解引用操作用于获取迭代器当前所指向的元素。对于双向迭代器,解引用操作与正向迭代器的解引用操作基本相同,只是需要考虑到它可能是基于其他迭代器类型实现的。

Ref operator*()
{
    return *_cur;
}

成员访问操作(operator->
成员访问操作用于获取迭代器所指向元素的成员指针。它的实现方式与反向迭代器的operator->操作类似,即通过解引用操作获取元素指针。

Ptr operator->()
{
    return &(*_cur);
}

比较操作(operator==operator!=

双向迭代器需要实现比较操作,以便能够判断两个迭代器是否指向相同的位置。比较操作的实现方式与反向迭代器类似,都是通过比较底层迭代器的位置来实现的。

bool operator==(const self& s)
{
    return (_cur == s._cur);
}
bool operator!=(const self& s)
{
    cometurn (_cur!= s._cur);
}

三、应用于vector

vector容器中应用双向迭代器,首先需要在vector类中定义双向迭代器类型。

template <class T>
class vector
{
public:
    //...

    //双向迭代器
    typedef __bidirectional_iterator<iterator, T&, T*> bidirectional_iterator;
    typedef __bidirectional_iterator<const_iterator, const T&, const T*> const_bidirectional_iterator;

    bidirectional_iterator begin() { return bidirectional_iterator(_start); }
    bidirectional_iterator end() { return bidirectional_iterator(_finish); }

    const_bidirectional_iterator begin() const { return const_bidirectional_iterator(_start); }
    const_bidirectional_iterator end() const { return const_bidirectional_iterator(_finish); }

    //...
private:
    iterator _start;
    iterator _finish;
    iterator _end_of_storage;
};

然后,可以使用双向迭代器对vector进行遍历。

void TestVectorWithBidirectionalIterator()
{
    vector<int> v = {1, 2, 3, 4, 5};

    vector<int>::bidirectional_iterator it = v.begin();
    while (it!= v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 反向遍历
    vector<int>::bidirectional_iterator rit = v.end();
    while (rit!= v.begin())
    {
        --rit;
        cout << *rit << " ";
    }
    cout << endl;
}

四、应用于list

对于list容器,同样可以应用双向迭代器。首先在list类中定义双向迭代器类型。

template <class T>
class list
{
public:
    //...

    //双向迭代器
    typedef __bidirectional_iterator<iterator, T&, T*> bidirectional_iterator;
    typedef __bidirectional_iterator<const_iterator, const T&, const T*> const_bidirectional_iterator;

    bidirectional_iterator begin() { return bidirectional_iterator(_head->next); }
    bidirectional_iterator end() { return bidirectional_iterator(_head); }

    const_bidirectional_iterator begin() const { return const_bidirectional_iterator(_head->next); }
    const_bidirectional_iterator end() const { return const_bidirectional_iterator(_head); }

    //...
private:
    // 头节点相关定义
    //...
};

然后使用双向迭代器对list进行遍历。

void TestListWithBidirectionalIterator()
{
    list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);

    list<int>::bidirectional_iterator it = l.begin();
    while (it!= l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 反向遍历
    list<int>::bidirectional_iterator rit = l.end();
    while (rit!= l.begin())
    {
        --rit;
        cout << *rit << " ";
    }
    cout << endl;
}

双向迭代器是 C++ STL 中的一种迭代器类型,它具有以下优点和缺点:

优点

  1. 双向遍历能力
    • 双向迭代器的核心优势在于它能够在容器中进行双向移动。这使得它在处理需要在容器中来回遍历的情况时非常方便。例如,在对一个容器进行查找操作时,如果从容器的一端开始查找没有找到目标元素,使用双向迭代器可以很容易地从另一端重新开始查找,而不需要重新创建迭代器或者使用复杂的逻辑来控制遍历方向。
    • 与只能单向遍历的迭代器(如正向迭代器只能向前,反向迭代器只能向后)相比,双向迭代器提供了更大的灵活性,能够更好地适应各种不同的算法和数据处理需求。
  2. 通用性和兼容性
    • 双向迭代器在设计上通常具有较高的通用性。它可以基于不同类型的底层迭代器进行实现,并且能够适应各种容器类型。例如,它可以在vectorlist等不同的容器中使用,只要这些容器提供了相应的底层迭代器支持。
    • 这种通用性使得双向迭代器能够与许多 STL 算法和函数很好地兼容。例如,很多 STL 算法都要求迭代器具有双向移动的能力,双向迭代器能够满足这些要求,从而可以方便地应用这些算法来处理容器中的元素。
  3. 代码简洁性和可读性
    • 在使用双向迭代器的代码中,由于它能够在一个迭代器对象上实现双向遍历,不需要像使用正向和反向迭代器那样分别使用不同的迭代器对象来实现双向遍历,因此可以使代码更加简洁。
    • 例如,在一个循环中既可以向前遍历容器又可以向后遍历容器,只需要通过改变迭代器的移动方向操作(++--)即可,这样的代码逻辑更加清晰,易于理解和维护。

缺点

  1. 性能开销
    • 与某些特定方向的迭代器(如正向迭代器在正向遍历vector时)相比,双向迭代器可能会带来一些性能开销。因为双向迭代器需要支持双向移动的功能,其内部实现可能会相对复杂一些,这可能会导致在某些情况下遍历速度稍慢。
    • 例如,在对一个大型vector进行连续的正向遍历时,如果使用双向迭代器,虽然它也能够正确地完成遍历,但由于其内部可能需要处理双向移动的逻辑,可能会比使用专门的正向迭代器稍微慢一些。
  2. 功能局限性
    • 尽管双向迭代器能够双向遍历容器,但它的功能仍然相对有限。它不像随机迭代器那样能够直接访问容器中的任意位置(在常数时间内)。如果需要频繁地随机访问容器中的元素,双向迭代器可能不是最佳选择。
    • 例如,在一个需要快速定位到容器中某个特定位置元素的算法中,双向迭代器可能无法提供足够高效的访问方式,可能需要额外的操作来实现定位,这会增加算法的复杂性和时间开销。

五、总结

双向迭代器是 C++ STL 中一种非常实用的迭代器类型,它结合了正向迭代器和反向迭代器的优点,能够在容器中进行双向遍历。通过合理地设计双向迭代器类,并在常见容器中应用它,可以提高代码的灵活性和通用性。在实际编程中,根据具体的需求选择合适的迭代器类型对于提高程序的效率和可读性非常重要。希望通过本文的介绍,读者能够对双向迭代器有更深入的理解和掌握。

相关推荐
朝九晚五ฺ几秒前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
qq_327342732 分钟前
Java实现离线身份证号码OCR识别
java·开发语言
锅包肉的九珍3 分钟前
Scala的Array数组
开发语言·后端·scala
心仪悦悦6 分钟前
Scala的Array(2)
开发语言·后端·scala
yqcoder29 分钟前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
baivfhpwxf202340 分钟前
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
开发语言·c#
许嵩6643 分钟前
IC脚本之perl
开发语言·perl
长亭外的少年1 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
直裾1 小时前
Scala全文单词统计
开发语言·c#·scala
心仪悦悦1 小时前
Scala中的集合复习(1)
开发语言·后端·scala