C++ STL 迭代器Iterator、五种迭代器类别

前言

最近在看《STL源码剖析》,并且也跟着在实现algorithm.h这个头文件。里面的算法基本都是对迭代器所指向的容器区间内的元素做操作,因此这些算法的泛型模板参数基本都有迭代器类型。针对不同的算法,对迭代器类型的要求也不同,本篇博客我就简单介绍一下C++中的五种迭代器类别。

五种迭代器类别的关系

首先介绍五种迭代器类别:

  1. 输入迭代器input iterator
  2. 输出迭代器output iterator
  3. 前向迭代器forward iterator
  4. 双向迭代器bidirectional iterator
  5. 随机访问迭代器random-access iterator

它们的继承关系如下图所示,越往下的迭代器功能越多:

在这里插入图片描述

以下表格主要是对它们的功能做个汇总。感觉表格的方式能够比较好对它们做对比、发现它们之间的联系:

迭代器类型能够进行的操作备注
输入迭代器
input iterator
1. 比较两个迭代器是否相等(==、!=)。
2. 前置和后置递增运算(++)(意味着它是单向移动的)。
3. 读取元素的解引用运算符(*)。只能读元素,也就是解引用只能出现在赋值运算符的右边。
4. 箭头运算符(->),解引用迭代器,并提取对象的成员。
5. 只能用于单遍扫描算法。
无。
输出迭代器
output iterator
1. 比较两个迭代器是否相等(==、!=)。
2. 前置和后置递增运算(++)(意味着它是单向移动的)。
3. 读取元素的解引用运算符(*)。只能写元素,也就是解引用只能出现在赋值运算符的左边。
4. 箭头运算符(->),解引用迭代器,并提取对象的成员。
5. 只能用于单遍扫描算法。
与输入迭代器的操作相似,只不过输入变成了输出。
前向迭代器
forward iterator
与输入迭代器的操作相似,只不过输入变成了输出。
1. 比较两个迭代器是否相等(==、!=)。
2. 前置和后置递增运算(++)(意味着它是单向移动的)。
3. 读取元素的解引用运算符(*)。可写也可读
4. 箭头运算符(->),解引用迭代器,并提取对象的成员。
5. 能用于多遍扫描算法。
输入迭代器与输出迭代器操作的结合。
双向迭代器
bidirectional iterator
1. 比较两个迭代器是否相等(==、!=)。
2. 前置和后置递增运算(++)。

3. 前置和后置递减运算(–)(意味着它是双向移动的)。
3. 读取元素的解引用运算符(*)。可写也可读
4. 箭头运算符(->),解引用迭代器,并提取对象的成员。
5. 能用于多遍扫描算法。
支持所有前向迭代器操作的基础上,支持递减运算符,也就是支持双向移动
随机访问迭代器
random-access iterator
1. 比较两个迭代器是否相等(==、!=),以及比较两个迭代器相对位置的关系运算符(<、<=、>和>=)。
2. 前置和后置递增运算(++)。
3. 支持和一个整数值的加减运算(+、+=、-、-=)。
4. 两个迭代器上的减法运算符(-),得到两个迭代器的距离。
5. 前置和后置递减运算(–)(意味着它是双向移动的)。
6. 读取元素的解引用运算符(*)。可写也可读
7. 箭头运算符(->),解引用迭代器,并提取对象的成员。
8. 支持下标运算符(iter[n]),与*(iter[n])等价,访问距离起始迭代器n个距离的迭代器指向的元素。
9. 能用于多遍扫描算法。
在支持双向移动的基础上,支持前后位置的比较、随机存取、直接移动n个距离。

使用案例

这里就在我自己实现的algorithm.h中找几个使用了以上几种迭代器类型的典型函数来介绍。

replace函数,如果迭代器区间内的元素等于旧值,则将其换为新值。这个过程需要读元素的值,也需要写元素的值,所以迭代器类型最低应该为前向迭代器ForwardIterator。可以使用更高级的迭代器,但是最低级只能用前向迭代器

// replace,旧值换成新值
template <class ForwardIterator, class T>
void replace(ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value) {
    for (; first != last; ++first) {
        if (*first == old_value)
            *first = new_value;
    }
}

merge函数,将两个有序序列合并,并输出到结果迭代器指向的序列。两个输入序列上的迭代器只负责对元素进行读,一个输出序列上的迭代器只负责对元素进行写,所以输入序列上的迭代器最低应该为输入迭代器InputIterator,输出序列上的迭代器最低应该为输出迭代器OutputIterator

// merge,将两个有序序列进行合并,返回结果序列最后一个元素的下一个位置
// 版本1,operator<
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator merge(InputIterator1 first1, InputIterator1 last1,
                     InputIterator2 first2, InputIterator2 last2,
                     OutputIterator result)  {
    while (first1 != last1 && first2 != last2) {
        if (*first1 < *first2) {
            *result = *first1;
            ++first1;
        }
        else {
            *result = *first2;
            ++first2;
        }
        ++result;
    }
    // 将两个序列中剩余的元素拷贝到result中
    return copy(first1, last1, copy(first2, last2, result));
}

reverse函数,将序列反转。不同的迭代器类型,可以用不同的实现方式。使用随机访问迭代器可以有更加高效的实现方式。所以这个函数设置为两层,最外层接口通过迭代器的类型(tag)来选择调用底层实现函数的哪个版本。随机访问迭代器RandomAccessIterator的版本可以直接通过比较大小来判断两者的位置,而双向迭代器BidirectionalIterator的版本只能用相等或者不相等来判断两者的位置。

// reverse,将序列反转
// 不同迭代器类型会影响性能,双向迭代器或者随机访问迭代器
// 因此需要设计两层函数,通过tag来区分不同的迭代器类型

// 底层函数,双向迭代器的版本,只能使用自增和自减
template <class BidirectionalIterator>
void __reverse(BidirectionalIterator first, BidirectionalIterator last, bidirectional_iterator_tag) {
    --last;
    while (first != last) {
        swap(*first, *last);
        if (++first == last)
            return;
        --last;
    }
}

// 底层函数,随机访问迭代器的版本
template <class RandomAccessIterator>
void __reverse(RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag) {
    // 修正last
    --last;
    // 随机访问迭代器的好处就是可以直接比较大小
    while (first < last) {
        swap(*first, *last);
        ++first;
        --last;
    }
}

// reverse对外接口
template <class BidirectionalIterator>
void reverse(BidirectionalIterator first, BidirectionalIterator last) {
    // 萃取出迭代器的类型
    typedef typename iterator_traits<BidirectionalIterator>::iterator_category iterator_category;
    __reverse(first, last, iterator_category());
}

不同类型迭代器的实现

每次设计一个迭代器时,可以在迭代器内部通过定义一个tag结构体来设定它的迭代器类别,同时在内部实现该迭代器类别支持的操作。然后在使用的时候通过iterator_traits来萃取出迭代器内部定义的tag,并通过tag类型来区分不同类别的迭代器。就像上面的reverse函数一样,通过tag类型调用不同迭代器类别对应的底层实现函数。

// 五种迭代器类别对应的tag
struct input_iterator_tag {};       // 只读迭代器
struct output_iterator_tag {};      // 只写迭代器
struct forward_iterator_tag : public input_iterator_tag {};     // 前向迭代器
struct bidirectional_iterator_tag : public forward_iterator_tag{};      // 双向迭代器
struct random_access_iterator_tag : public bidirectional_iterator_tag{};// 随机访问迭代器,支持指针的所有运算操作

// 具体的某个迭代器实现
// 任何迭代器都应该提供五种类型,以利于traits萃取,否则便是自别于整个STL架构,可能无法与其他STL组件顺利搭配
// 所以STL提供了一个iterators class,如果每个新设计的迭代器都继承自它,则可保证符合STL所需之规范
// 因为主要是为了实现萃取五种类型的功能,所以迭代器中只有这几种类型的信息
// Category被定义为迭代器类型,实参应该为上面五个tag的其中一个
template <class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T&>
struct iterator {
    typedef Category    iterator_category;
    typedef T           value_type;
    typedef Distance    difference_type;
    typedef Pointer     pointer;
    typedef Reference   reference;
};


// traits相当于萃取机,通过traits可以获得迭代器中的五种类型
// 一般类型的traits,获取迭代器中的类型信息
template <class Iterator>
struct iterator_traits {
    typedef typename Iterator::iterator_category iterator_category;     // 普通类型的迭代器类型,类型值为五个迭代器tag结构体类型的其中一个
    typedef typename Iterator::value_type        value_type;            // 普通类型的类型
    typedef typename Iterator::difference_type   difference_type;       // 普通类型的迭代器中的指针差值类型
    typedef typename Iterator::pointer           pointer;               // 普通类型的迭代器中的指针类型
    typedef typename Iterator::reference         reference;             // 普通类型的迭代器中的引用类型
};


// 针对原生指针类型设计的traits偏特例化版本(特殊化)
// 原生指针也算是迭代器的一种
// 通过将模板参数设定为T*来进行模板参数特例化
// 表明只有模板参数为原生指针类型时,才会使用这个模板类
// 相当于将模板参数定为原生指针类型,所以五种类型不需根据迭代器中的信息就可以提前知道
// 也就是当T为原生指针类型时的五种类型
template <class T>
struct iterator_traits<T*> {
    typedef random_access_iterator_tag  iterator_category;	// 原生指针应该是支持随机访问的,所以tag是随机访问的tag
    typedef T                           value_type;
    typedef ptrdiff_t                   difference_type;
    typedef T*                          pointer;
    typedef T&                          reference;
};

// reverse对外接口
template <class BidirectionalIterator>
void reverse(BidirectionalIterator first, BidirectionalIterator last) {
    // 萃取出迭代器的类型,实际上iterator_category是上述五种tag结构体的其中一种
    typedef typename iterator_traits<BidirectionalIterator>::iterator_category iterator_category;
    __reverse(first, last, iterator_category());
}

以下是设计一个list的迭代器。它是一个双向迭代器bidirectional_iterator,所以可以看到它的tag就是bidirectional_iterator_tag,而且内部重载的运算符,都是双向迭代器支持的运算符,不支持的运算符绝对不会进行重载

template <class T, class Ref, class Ptr>
struct __list_iterator {
    typedef __list_iterator<T, T&, T*>      iterator;   // 迭代器类型
    typedef __list_iterator<T, Ref, Ptr>    self;       //

    typedef bidirectional_iterator_tag iterator_category;   // 迭代器的类型为双向迭代器。tag用于在调用一些底层函数时识别这个迭代器的类型
    typedef T value_type;               // 元素的类型
    typedef Ptr pointer;                // 元素的指针类型
    typedef Ref reference;              // 元素的引用类型
    typedef __list_node<T>* link_type;  // 指向节点的指针类型
    typedef size_t size_type;           // 元素数目的类型
    typedef ptrdiff_t difference_type;  // 迭代器差值的类型

    link_type node;     // 迭代器内部的指向节点的指针

    // 构造函数
    __list_iterator() = default;
    __list_iterator(link_type x) : node(x) {}
    __list_iterator(const iterator& x) : node(x.node) {}

    // 接着重载运算符
    bool operator==(const self& x) const { return node == x.node; }
    bool operator!=(const self& x) const { return node != x.node; }

    // 解引用,取数据值
    reference operator*() const { return node->data; }
    // 成员存取运算子的标准做法
    pointer operator->() const { return &(operator*()); }

    // 最后是自增自减
    // 前缀自增
    // 返回的是当前对象(*this),所以可以返回引用
    self& operator++() {
        // node指向下一个节点
        node = (link_type)(node->next);
        return *this;
    }
    // 后缀自增
    // 返回的是临时对象,所以不能返回引用
    self operator++(int) {
        self tmp = *this;
//        this->node = (link_type)(node->next);
        ++*this;
        return tmp;

    }

    // 前缀自减
    self& operator--() {
        node = (link_type)(node->prev);
        return *this;
    }

    // 后缀自减
    self operator--(int) {
        self tmp = *this;
        --*this;
        return tmp;
    }
};
  • 23
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值