Loading

迭代器剖析

对于迭代器,我们要有以下两点认识:

  1. 迭代器可以理解为泛化的指针,可以执行 *iter(解引用)、iter++(正向迭代器)、iter--(双向迭代器)、iter+=i(随机访问迭代器)、iter->method() 等操作,这与指针的使用非常类似;
  2. 迭代器是容器与算法之间的桥梁,迭代器让算法忽略了容器底层的数据结构,使用迭代器可以获知容器元素的类型、完成对容器元素的访问与遍历。

从指针的角度理解迭代器

从底层结构上来看,所有容器的数据结构可以分为顺序表(连续内存)和链表(不连续内存),前者如 array、vector,而它们的迭代器本质上就是对其指针的一层封装;后者如 list、forward_list,它们的迭代器无法单单用指针完成,因而必须创建对应的迭代器类,并重载*、++、--、->等运算符。

因此,我们分别以 vector 和 list 为例介绍迭代器。

vector 的迭代器

vector 的实现仅需要三个指针:start、finish、end_of_storage,其中 start 指向 vector 的第一个元素,finish 指向已使用 vector 的最后一个元素的下一个位置,end_of_storage 指向 vector 容量的最后一个元素的下一个元素。因此,在 32 位平台上,对 vector 做 sizeof,无论 vector 内部存储什么类型的元素,无论 vector 内部存储几个元素,其结构都是 3×4=12 字节。

在 SGI-STL V3.3中,vector中相关实现如下:

template <class _Tp, class _Alloc> 
class _Vector_base {
public:
  // ...
  _Vector_base(const _Alloc&)						// 创建一个空的分配器
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
  _Vector_base(size_t __n, const _Alloc&)			// 创建一个含有n个字节的分配器
      												// 此处的__n不是元素个数,而是经过计算的需要分配的字节数
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) 
  {
    _M_start = _M_allocate(__n);					// start指针指向分配空间的开始
    _M_finish = _M_start;
    _M_end_of_storage = _M_start + __n;				// end_of_storage指向分配空间末尾的下一个位置
  }

  ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }

protected:
  _Tp* _M_start;				// start 指针
  _Tp* _M_finish;				// finish 指针
  _Tp* _M_end_of_storage;		// end_of_storage 指针
    
  // ...
};

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc> 
{
  // ...
public:
  typedef _Tp value_type;
  typedef value_type* iterator;					// iterator类型即_Tp*类型
  typedef value_type& reference;
  typedef size_t size_type;

public:
  iterator begin() { return _M_start; }			// begin()即返回start指针
  iterator end() { return _M_finish; }			// end()即返回finish指针

  size_type size() const						// 返回容器内元素个数
    { return size_type(end() - begin()); }
  size_type max_size() const					// 返回容器内所能存储的最大元素个数
    { return size_type(-1) / sizeof(_Tp); }
  size_type capacity() const					// 返回容器的容量
    { return size_type(_M_end_of_storage - begin()); }
  bool empty() const							// 容器是否未空
    { return begin() == end(); }

  reference operator[](size_type __n) { return *(begin() + __n); }
    											// 返回 vector[n]
  
  // ...
}

从上述代码可知,vector 的迭代器类型为 _Tp* ,即指针类型;begin()、end()、size()、capacity()、empty() 等仅需对指针进行简单运算即可,另外也不需要对 *、++、--、->等运算符函数进行重载,因为指针本就支持这些操作。

list 的迭代器

list 是双向链表,其底层实现为环状双向链表,由于链表中每个结点的存储空间并不连续,因此并不能直接使用指针实现迭代器的功能。

list 的底层实现

首先我们看一下 list 的底层实现(gcc 2.95版本):

list 为一个环状双向链表,在链表尾部有一个空白结点,以实现STL的前闭后开区间特性,即迭代器end()指向这个空白结点(最后一个位置的下一个位置)。在 list 类中,唯一一个数据成员是一个指向结点的指针,这个指针(即下图中的 node)就指向链表尾部的空白结点。因此,list的大小在 32 为平台上为 4 字节。

在创建 list 时,list 的构建函数会创建这个空白结点,并让 node 指针指向空白结点,并使自身成为环状链表(没有任何元素)。当链表中含有元素时,node 指向的空白结点的 next 指针指向链表中的第一个元素。因此,list.end() 即返回 node,list.begin() 即返回 node.next 。

template <class _Tp>
struct _List_node {						// 链表结点
  typedef void* _Void_pointer;
  _Void_pointer _M_next;				// next指针
  _Void_pointer _M_prev;				// prev指针
  _Tp _M_data;							// data
};

template <class _Tp, class _Alloc>
class _List_base						// list 的基类
{
public:
  // ...
  _List_base(const allocator_type&) {
    _M_node = _M_get_node();			// 分配一个结点
    _M_node->_M_next = _M_node;
    _M_node->_M_prev = _M_node;
  }
protected:
  _List_node<_Tp>* _M_get_node() { return _Alloc_type::allocate(1); }
  _List_node<_Tp>* _M_node;				// 唯一数据成员-指针,其类型为指向链表结点_List_node的指针
};

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class list : protected _List_base<_Tp, _Alloc> {	// list模板类
	// 无任何成员变量
public:
  typedef _List_iterator<_Tp,_Tp&,_Tp*> iterator;	// list 的迭代器类型
  iterator begin() { return (_Node*)(_M_node->_M_next); }
  iterator end() { return _M_node; }
  bool empty() const { return _M_node->_M_next == _M_node; }
}

list 迭代器的实现

list 使用类实现其迭代器,而这个类本质上也只是对指针的包装,并重载了对应的函数。

template<class _Tp, class _Tp&, class _Tp*>
struct _List_iterator {
  // ...
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             _Self;
  typedef _Tp value_type;
  typedef _Tp& pointer;
  typedef _Tp* reference;
  typedef _List_node<_Tp> _Node;

  _Node* _M_node;						// 唯一的数据成员-指向链表结点的指针

  // 重载操作符函数
  bool operator==(const _Self& __x) const { return _M_node == __x._M_node; }
  bool operator!=(const _Self& __x) const { return _M_node != __x._M_node; }
  
  reference operator*() const { return (*_M_node)._M_data; }
  pointer operator->() const { return &(operator*()); }
  _Self& operator++() { 				// ++指针,即指向下一个结点
    _M_node = (_Node*)(_M_node->_M_next);
    return *this;
  }
  _Self operator++(int) { 				// 指针++
    _Self __tmp = *this;				// 调用拷贝构造函数
    ++*this;
    return __tmp;
  }
  // ...
};

在新版的GNU C++ 4.9 中 list 的实现有些许不同,但 iterator 的实现依旧是同样的逻辑,这里不再赘述。

从桥梁的角度理解迭代器

正如“从指针的角度理解迭代器”中介绍的那样,无论是顺序表的迭代器还是链表的迭代器,本质都是指针。在上一节中,指针被封装成迭代器,实现了 *、++、--、-> 等运算,但从算法的角度来说,算法不仅需要这些迭代器操作,算法还需要知道容器内存储的元素类型以及其他信息(这里同称为 associated types),而迭代器必须就这些问题给出回答。

迭代器必须提供以下五种 associated types(以 __list_iterator 为例):

template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator {
  // ...
  typedef bidirectional_iterator_tag iterator_category;	// 表示迭代器类型(正向、双向、随机)
  typedef _Tp value_type;								// 表示迭代器指向的元素类型
  typedef _Ptr pointer;									// 表示迭代器指向的元素的指针类型,即_Tp*
  typedef _Ref reference;								// 表示迭代器指向的元素的引用类型,即_Tp&
  typedef ptrdiff_t difference_type;					// 表示两个迭代器距离的类型,ptrdiff_t一般是unsigned(长)整型
  // ...
}

如果迭代器以类实现,那么算法只需使用域操作符即可获知对应的 associated type:

template <typename I>
void alogrithm(I iter) {
	// ...
	I::iterator_category
	I::value_type
	// ...
}

但如果迭代器是指针呢?如 vector 和 array,那么就无法通过上述方法获取对应的 associated type。

而这里,只需要加一个中间类 Iterator_traits,即可解决问题:

  template<typename _Iterator>		// class 类型的 iterator 实现
    struct __iterator_traits<_Iterator,
			     __void_t<typename _Iterator::iterator_category,
				      typename _Iterator::value_type,
				      typename _Iterator::difference_type,
				      typename _Iterator::pointer,
				      typename _Iterator::reference>>
    {
      typedef typename _Iterator::iterator_category iterator_category;
      typedef typename _Iterator::value_type        value_type;
      typedef typename _Iterator::difference_type   difference_type;
      typedef typename _Iterator::pointer           pointer;
      typedef typename _Iterator::reference         reference;
    };

  template<typename _Iterator>
    struct iterator_traits
    : public __iterator_traits<_Iterator> { };

对于指针类型的迭代器,只需对上述的类模板进行特化:

  /// Partial specialization for pointer types.
  template<typename _Tp>
    struct iterator_traits<_Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef _Tp*                        pointer;
      typedef _Tp&                        reference;
    };

  /// Partial specialization for const pointer types.
  template<typename _Tp>
    struct iterator_traits<const _Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef const _Tp*                  pointer;
      typedef const _Tp&                  reference;
    };

这样,无论是 class 类型的迭代器还是指针类型的迭代器,都可以通过 iterator_traits 获取对应的 associated types。

posted @ 2023-11-21 14:36  Lowell_liu  阅读(24)  评论(0编辑  收藏  举报