C++ STL迭代器的学习教程

2021-08-30 11:32:51 浏览数 (3061)

C++ STL 迭代器

了解如何使用 C++ 标准模板库 (STL) 的容器的关键之一是了解迭代器的工作原理。lists 和maps等容器的行为不像数组,因此您不能使用for循环来遍历其中的元素。同样,因为这些容器不能随机访问,所以不能使用简单的整数索引。你可以使用迭代器来引用容器的元素。

STL 容器和算法可以很好地协同工作的原因是它们彼此一无所知 - Alex Stepanov

迭代器是类似指针的对象,它允许程序在不暴露底层表示的情况下顺序地遍历容器的元素。迭代器可以通过递增和递减它们从一个元素前进到下一个元素。每个容器类型都有一个与之关联的不同迭代器。例如,迭代器 forlist<int>声明为:

 std::list<int>::iterator

迭代器类别

迭代器分为几类,因为不同的算法需要使用不同的迭代器。例如,该std::copy()算法需要一个可以通过递增来推进的迭代器,而该 std::reverse()算法需要一个可以递减的迭代器。在 C++ 语言中,该标准定义了五个不同的类别。

  • 输入迭代器
    • 只读且只能读取一次。
    • 例子: std::istream_iterator(istream& is)
  • 输出迭代器
    • 只写
    • 例如:std::ostream_iterator<int> out_it (std::cout,", ");
  • 前向迭代器
    • 收集输入+输出迭代器
    • 示例:std::forward_list::iterator,std::unordered_map::iterator
  • 双向迭代器
    • 像前向迭代器,但也有 operator–
    • 例子: std::list::iterator
  • 随机访问迭代器
    • 已重载operator[],指针运算
    • 例子:std::vector<int>::iterator

你可以在此处获得有关这些的更多信息

迭代器特征

迭代器特征允许算法以统一的方式访问有关特定迭代器的信息,以避免在需要遍历不同样式的容器时为每个特定情况重新实现所有迭代器。例如,查找元素std::listO(n)复杂性,而std::vector随机访问元素是O(1)复杂性(给定索引位置)。算法最好知道可以使用+=运算符(随机访问)或仅使用++运算符(转发)遍历容器,以选择更好的选择以降低计算的算法的复杂度。

迭代器特征如下:

  • difference_type
    • 表示迭代器距离的类型
    • 迭代器的类型差异p2 - p1
  • value_type
    • 迭代器指向的值的类型
  • pointer
    • 迭代器指向的指针值
    • 通常 value_type*
  • reference
    • 迭代器指向的引用值
    • 通常 value_type&
  • iterator category

    • 标识由迭代器建模的迭代器概念。
    • 以下之一:
    struct input_iterator_tag {};
    struct output_iterator_tag {};
    struct forward_iterator_tag : input_iterator_tag {};
    struct bidirectional_iterator_tag : forward_iterator_tag {};
    struct random_access_iterator_tag : bidirectional_iterator_tag {};
    

的定义iterator_traits看起来像:

// The basic version works for iterators with the member type
template <class Iterator>
struct iterator_traits
{
    typedef typename Iterator::value_type value_type;
    typedef typename Iterator::difference_type difference_type;
    typedef typename Iterator::pointer pointer;
    typedef typename Iterator::reference reference;
    typedef typename Iterator::iterator_category iterator_category;
};

// A partial specialization takes care of pointer types
template <class T>
struct iterator_traits<T *>
{
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T *pointer;
    typedef T &reference;
    typedef random_access_iterator_tag iterator_category;
};

// pointers to const type
template <class T>
struct iterator_traits<const T *>
{
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef const T *pointer;
    typedef const T &reference;
    typedef random_access_iterator_tag iterator_category;
};

有时,泛型算法需要知道其迭代器参数的值类型,即迭代器指向的类型。例如,要交换两个迭代器指向的值,就需要一个临时变量。

template <class Iterator>
void swap (Iterator a, Iterator b) 
{
  typename Iterator::value_type tmp = *a;
  *a = *b;
  *b = tmp;
}

这些特征还通过利用iterator_category成员提供的有关基本迭代器类别的知识来提高算法的效率算法可以使用这个“标签”来选择迭代器能够处理的最有效的实现,而不会影响处理各种迭代器类型的灵活性。

在下面的例子中,我们的目标是有一个单一的advance算法,可以根据迭代器类别自动执行正确的版本。

template <class InputIterator, class Distance>
void advance(InputIterator &i, Distance n,
             input_iterator_tag)
{
    for (; n > 0; --n)
        ++i;
}

template <class BidirectionalIterator, class Distance>
void advance(BidirectionalIterator &i, Distance n
                                           bidirectional_iterator_tag)
{
    if (n <= 0)
        for (; n > 0; --n)
            ++i;
    else
        for (; n < 0; ++n)
            --i;
}

template <class RandomAccessIterator, class Distance>
void advance(RandomAccessIterator &i, Distance n,
             random_access_iterator_tag)
{
    i += n;
}

// Generic advance algorithm using compile-time dispatching based on function overloading
template <class InputIterator, class Distance>
void advance(InputIterator i, Distance n)
{
    advance(i, n, typename iterator_traits<Iterator>::iterator_category());
}

编写自定义迭代器

迭代器特征将自动适用于定义适当成员类型的任何迭代器类。自定义迭代器应该支持以下指针:

  • 如何检索该点的值
  • 如何增加/减少迭代点
  • 如何与其他迭代点进行比较
#include <algorithm>
#include <exception>
#include <iostream>
#include <iterator>
#include <typeinfo>
#include <vector>

template <typename ArrType> 
class MyArray {
private:
  ArrType *m_data;
  unsigned int m_size;

public:
  class Iterator {
  public:
    // iterator_trait associated types
    typedef Iterator itr_type;
    typedef ArrType value_type;
    typedef ArrType &reference;
    typedef ArrType *pointer;
    typedef std::bidirectional_iterator_tag iterator_category;
    typedef std::ptrdiff_t difference_type;

    Iterator(pointer ptr) : m_itr_ptr(ptr) {}
    itr_type operator++() {
      itr_type old_itr = *this;
      m_itr_ptr++;
      return old_itr;
    }

    itr_type operator++(int dummy) {
      m_itr_ptr++;
      return *this;
    }

    itr_type operator--() {
      itr_type old_itr = *this;
      m_itr_ptr--;
      return old_itr;
    }

    itr_type operator--(int dummy) {
      m_itr_ptr--;
      return *this;
    }

    reference operator*() const { return *m_itr_ptr; }

    pointer operator->() const { return m_itr_ptr; }

    bool operator==(const itr_type &rhs) { return m_itr_ptr == rhs.m_itr_ptr; }

    bool operator!=(const itr_type &rhs) { return m_itr_ptr != rhs.m_itr_ptr; }

  private:
    pointer m_itr_ptr;
  };

  MyArray(unsigned int size) : m_size(size) { m_data = new ArrType[m_size]; }

  unsigned int size() const { return m_size; }

  ArrType &operator[](unsigned int idx) {
    if (idx >= m_size)
      throw std::runtime_error("Index out of range");
    return m_data[idx];
  }

  Iterator begin() { return Iterator(m_data); }

  Iterator end() { return Iterator(m_data + m_size); }
};

int main()
{
  MyArray<double> arr(3);
  arr[0] = 2.6;
  arr[1] = 5.2;
  arr[2] = 8.9;

  std::cout << "MyArray Contents: ";
  for (MyArray<double>::Iterator it = arr.begin(); it != arr.end(); it++) {
    std::cout << *it << " ";
  }

  std::cout << std::endl;

  std::vector<double> vec;
  std::copy(arr.begin(), arr.end(), std::back_inserter(vec));

  std::cout << "Vector Contents after copy: ";
  for (std::vector<double>::iterator it = vec.begin(); it != vec.end(); it++) {
    std::cout << *it << " ";
  }

  std::cout << std::endl;

  std::cout << typeid(std::iterator_traits<
                          MyArray<double>::Iterator>::iterator_category())
                   .name()
            << std::endl;
  return 0;
}

/*OUTPUT
MyArray Contents: 2.6 5.2 8.9 
Vector Contents after copy: 2.6 5.2 8.9 
FSt26bidirectional_iterator_tagvE
*/

迭代器和循环范围

基于范围的 for 循环(或简称为 range-for)以及auto,是 C++11 标准中添加的最重要的特性之一。

范围for循环的语法模板如下所示:

for (range_declaration : range_expression) 
{ 
    // loop body 
}

在 C++11/C++14 中,上述格式产生类似于以下的代码:

{  
  auto&& range = range_expression ; 
  // beginExpr is range.begin() and endExpr is range.end()
  for (auto b = beginExpr, e = endExpr; b != e; ++b) { 
    range_declaration = *b; 
    // loop body
  } 
} 

基于范围的 for 循环的典型用法:

// Iterate over STL container
std::vector<int> v{1, 2, 3, 4};
for (const auto &i : v)
    std::cout << i << "\n";

range for 循环的工作方式是创建一个指向向量第一个元素的迭代器,然后依次访问向量的每个元素,直到迭代器到达向量的最后一个元素,然后循环终止。在cppinsight可以观察到这种现象在这里