STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,彼此独立设计,最后再用迭代器(Iterator)把他们粘合在一起。可见迭代器在STL中的重要程度。迭代器已经作为一种设计思想被记录与《设计模式》中,它的意图在于“提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示”。
迭代器的作用其实相当于一个智能指针,它指向容器内部的数据,可以通过operator *操作符来解指针获得数据的值,也可以通过operator ->操作符来获取数据的指针,还能够重载++,--等运算符来移动指针。
1 概述
迭代器可以分为五类:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机存取迭代器。STL中用五个类来代表这五种迭代器类别:
![]() | ![]() | ![]() | ||||
![]() |
The characteristics of each category of iterators are:
category | characteristic | valid expressions | |||
---|---|---|---|---|---|
all categories | Can be copied and copy-constructed | X b(a); b = a; | |||
Can be incremented | ++a a++ *a++ | ||||
Random Access | Bidirectional | Forward | Input | Accepts equality/inequality comparisons | a == b a != b |
Can be dereferenced as an rvalue | *a a->m | ||||
Output | Can be dereferenced to be the left side of an assignment operation | *a = t *a++ = t | |||
Can be default-constructed | X a; X() | ||||
Can be decremented | --a a-- *a-- | ||||
Supports arithmetic operators + and - | a + n n + a a - n a - b | ||||
Supports inequality comparisons (<, >, <= and >=) between iterators | a < b a > b a <= b a >= b | ||||
Supports compound assignment operations += and -= | a += n a -= n | ||||
Supports offset dereference operator ([]) | a[n] |
不同类型的迭代器功能也不一。读和写就需要使用不同的功能。随机存取对于一个”vector”既高效的又方便的,但是,对于”list”是代价高的。
不同的容器提供了不同类型的迭代器
迭代器类型 | 描述 | 提供者 | 标签Tag |
---|---|---|---|
输入(Input) | 通过前移操作读取数据。 这种迭代器可以前移,可以比较,也可以解除引用。 | istream | input_iterator_tag |
输出(Output) | 通过前移动作写入数据。 这种迭代器可以前移,也可以解除引用。 | ostream, inserter | output_iterator_tag |
前向(Forward) | 通过前移操作读写数据。结合了输入迭代器和输出迭代器的功能,能够存储迭代器的值。 | slist | forward_iterator_tag |
双向(Bidirectional) | 通过前移操作和后移操作读写数据。这些迭代器类似于前向迭代器,但是,可以对其进行前移或者后移。 | list, map, multimap, set, multiset | bidirectional_iterator_tag |
随机存取(Random-access) | 随机读写数据,是功能最强的迭代器,结合了双向迭代器的功能,能够进行指针算术运算和指针比较运算。 | array, deque, string, vector | random_access_iterator_tag |
每个STL容器都关联一类迭代器,每个STL 算法均使用一定类型的迭代器。 每种容器都会有iterator , const_iterator,至于所属的类型要由具体的容器决定,如vector里面的iterator是random_access_iterator
迭代器定义如下
template <class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator {
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
typedef Category iterator_category;
};
value_type: 代表迭代器所指对象的类型。
difference_type:代表两个迭代器之间的距离
reference_type:代表迭代器所指对象的引用类型。简言之,它是operator*()的返回类型
pointer_type:代表迭代器所致对象的指针类型。简言之,它是operator->()的返回类型
iterator_category:代表1中提出的五种迭代器的类型标识
begin 和 end的位置
2 iterator_traits
为了方便算法的使用,iterator_traits会将每种容器定义的五种类型萃取出来:
1 template <class _Iterator>
2 struct iterator_traits {
3 typedef typename _Iterator::iterator_category iterator_category;
4 typedef typename _Iterator::value_type value_type;
5 typedef typename _Iterator::difference_type difference_type;
6 typedef typename _Iterator::pointer pointer;
7 typedef typename _Iterator::reference reference;
8 };
iterator_traits扮演了类似于下图的一个角色:
iterator_traits为屏蔽了下层各容器类型的不同,为上层的应用(主要是一些算法,如advance()等)提供了统一的用户界面。
如我们要实现一个模板的find函数,因为我们不知道要返回什么类型,所以可以用iterator_traitstemplate <typename iter, typename T>
iterator_traits<T>::value_type find(iter begin, iter end, T& value);
3 迭代器的适配器
STL提供了许多基于迭代器的适配器,如back_insert_iterator, front_insert_iterator, inser_iterator, reverse_iterator, istream_iterator, ostream_iterator, istreambuf_iterator, ostreambuf_iterator等。
这些适配器大致可以分为三类:插入迭代器、反向迭代器和IO迭代器。下面一一介绍这三类迭代器,重点会放在反向迭代器上。
3.1 插入迭代器
插入迭代器用于将值插入到容器中。插入迭代器是一个模板类,模板参数为容器,迭代器只需要在重载操作符函数operator=()中调用容器的插入操作(对应的push_back, push_front或insert)即可。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
为什么需要插入迭代器呢,因为对于copy函数,并没有为destination申请空间,如果空间不足就会导致不可预料的结果,而使用插入迭代器就可以解决这个问题,他可以使得destination的空间相应的增长。
back_insert :只有提供push_back的容器才能使用,如vector,deque,list
front_insert: 只有提供push_front的容器才能使用,如 deque,list
inserters: 所有类型的容器都可以使用,对于associative容器,只能用这个,并且pos指定为begin,容器会根据值的大小找相应的位置插入
3.2 反向迭代器
顾名思义,反向迭代器会提供与普通迭代器相反方向的遍历功能。反向迭代器其实一个正向迭代器的适配器,它的实现都是通过调用正向迭代器的操作,为了与迭代器的概念保持一致(begin指向迭代器的第一个元素,end指向迭代器的最后一个元素的下一个位置),又与正向迭代器有一点点不同。
reverse_iterator的实现中有一个名为current的Iterator,它是模板参数,即正向迭代器。正向迭代器指向的范围是序列中的第一个元素到最后一个元素的下一个位置,为了保持迭代器概念的统一,反向迭代器的rbegin应该是序列的最后一个元素,rend应该是第一个元素前面的元素。
所以,current总是指向reverse_iterator所指元素之后的一个元素。这也意味这*返回的是值*(current-1),++通过对current的--实现。下面贴上reverse_iterator的代码。
1 template <class _Iterator>
2 class reverse_iterator
3 {
4 protected:
5 _Iterator current;
6 public:
7 typedef typename iterator_traits<_Iterator>::iterator_category
8 iterator_category;
9 typedef typename iterator_traits<_Iterator>::value_type
10 value_type;
11 typedef typename iterator_traits<_Iterator>::difference_type
12 difference_type;
13 typedef typename iterator_traits<_Iterator>::pointer
14 pointer;
15 typedef typename iterator_traits<_Iterator>::reference
16 reference;
17
18 typedef _Iterator iterator_type;
19 typedef reverse_iterator<_Iterator> _Self;
20
21 public:
22 reverse_iterator() {}
23 explicit reverse_iterator(iterator_type __x) : current(__x) {}
24
25 reverse_iterator(const _Self& __x) : current(__x.current) {}
26 #ifdef __STL_MEMBER_TEMPLATES
27 template <class _Iter>
28 reverse_iterator(const reverse_iterator<_Iter>& __x)
29 : current(__x.base()) {}
30 #endif /* __STL_MEMBER_TEMPLATES */
31
32 iterator_type base() const { return current; }
33 reference operator*() const {
34 _Iterator __tmp = current;
35 return *--__tmp;
36 }
37 #ifndef __SGI_STL_NO_ARROW_OPERATOR
38 pointer operator->() const { return &(operator*()); }
39 #endif /* __SGI_STL_NO_ARROW_OPERATOR */
40
41 _Self& operator++() {
42 --current;
43 return *this;
44 }
45 _Self operator++(int) {
46 _Self __tmp = *this;
47 --current;
48 return __tmp;
49 }
50 _Self& operator--() {
51 ++current;
52 return *this;
53 }
54 _Self operator--(int) {
55 _Self __tmp = *this;
56 ++current;
57 return __tmp;
58 }
59
60 _Self operator+(difference_type __n) const {
61 return _Self(current - __n);
62 }
63 _Self& operator+=(difference_type __n) {
64 current -= __n;
65 return *this;
66 }
67 _Self operator-(difference_type __n) const {
68 return _Self(current + __n);
69 }
70 _Self& operator-=(difference_type __n) {
71 current += __n;
72 return *this;
73 }
74 reference operator[](difference_type __n) const { return *(*this + __n); }
75 };
3.3 IO迭代器
标准库提供4个迭代器类型,以使流能够融入容器和算法的框架中:
ostream_iterator: 用于向ostream中写入
istream_iterator: 用于向istream中读出
ostreambuf_iterator: 用于向流缓冲区写入
istreambuf_iterator: 用于从流缓冲区读出
输入输出迭代器的思想是将输入输出流当作序列,ostream_iterator和istream_iterator相当于指向序列的迭代器,用户可以通过这个迭代器对输入输出流做操作。但是,ostream_iterator的迭代器类型是input_iterator,只支持写操作(*p=X)和迭代操作(++);istream_iterator的迭代器类型是output_iterator,它可以支持读(=*p), 访问(->),迭代(++),比较(==, !=)操作。
下面的代码是ostream_iterator的实现代码:
1 template <class _Tp,
2 class _CharT = char, class _Traits = char_traits<_CharT> >
3 class ostream_iterator {
4 public:
5 typedef _CharT char_type;
6 typedef _Traits traits_type;
7 typedef basic_ostream<_CharT, _Traits> ostream_type;
8
9 typedef output_iterator_tag iterator_category;
10 typedef void value_type;
11 typedef void difference_type;
12 typedef void pointer;
13 typedef void reference;
14
15 ostream_iterator(ostream_type& __s) : _M_stream(&__s), _M_string(0) {}
16 ostream_iterator(ostream_type& __s, const _CharT* __c)
17 : _M_stream(&__s), _M_string(__c) {}
18 ostream_iterator<_Tp>& operator=(const _Tp& __value) {
19 *_M_stream << __value;
20 if (_M_string) *_M_stream << _M_string;
21 return *this;
22 }
23 ostream_iterator<_Tp>& operator*() { return *this; }
24 ostream_iterator<_Tp>& operator++() { return *this; }
25 ostream_iterator<_Tp>& operator++(int) { return *this; }
26 private:
27 ostream_type* _M_stream;
28 const _CharT* _M_string;
29 };
用户可以像下面一样使用ostream_iterator:
1 ostream_iterator<string> os(cout);
2 *++os = "cobb";
3 *os = "liu";
上面的这段程序会产生输出cobbliu。在使用ostream_iterator的时候需要像使用正常迭代器一样将迭代器作迭代操作(++)。
istream_iterator的实现跟ostream_iterator的实现大相径庭,值得注意的是istream_iterator中有一个标志标识输入序列是否结束,它的默认构造函数会将istream_iterator指向输入序列的结束位置(M_ok=false),表示输入序列结束。当对输入迭代器作迭代操作(++)的时候,会判断M_ok的值,如果该值为false,则终止读过程。
istreambuf_iterator和ostreambuf_iterator可以使用户跨过iostream,直接跟流缓冲区打交道。
4 其他
++iter 比 iter++要快,因为前者返回的是新的值,不需要存储旧的值;后者返回的是旧的值,要把旧的值存储起来
几个有用的函数,#include <iterator>
void advance (InputIterator& pos, Dist n) ,可以向前或向后移动n,对非random iterator也适用的
Dist distance (InputIterator pos1, InputIterator pos2)
#include <algorithm>
void iter_swap (ForwardIterator1 pos1, ForwardIterator2 pos2) //pos1,pos2的类型可以不一样,但其值必须是可相互赋值的
5 实现原理
下面以List为例说明迭代器的原理
-
// List节点的定义 template <class T> struct __list_node { typedef void* void_pointer; void_pointer next; void_pointer prev; T data; }; // List迭代器的定义 template<class T, class Ref, class Ptr> struct __list_iterator { // 这三个typedef是为了简化后面的代码书写 typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; typedef __list_iterator<T, Ref, Ptr> self; typedef bidirectional_iterator_tag iterator_category; // 迭代器类型属于bidirectional iterator 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(link_type x) : node(x) {} __list_iterator() {} __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*()); // 前置++操作符,指向下一个节点 self& operator++() { node = (link_type)((*node).next); return *this; } // 后置++操作符,指向下一个节点 self operator++(int) { self tmp = *this; ++*this; return tmp; } // 前置--操作符,指向前一个节点 self& operator--() { node = (link_type)((*node).prev); return *this; } // 后置--操作符,指向前一个节点 self operator--(int) { self tmp = *this; --*this; return tmp; } }; template <class T, class Alloc = alloc> class list { ... ... public: typedef __list_iterator<T, T&, T*> iterator; // 注意iterator所用的就是__list_iterator typedef __list_iterator<T, const T&, const T*> const_iterator; ... ... protected: link_type node; // 头节点,该List其实是一个带头节点的双向循环链表 public: list() { empty_initialize(); } iterator begin() { return (link_type)((*node).next); } // 返回头节点的下一个节点,即第一个节点的iterator const_iterator begin() const { return (link_type)((*node).next); } iterator end() { return node; } // 返回头节点的iterator,其实就是返回链表的结尾 const_iterator end() const { return node; } ... ... } 如果我们对List容器使用find算法,这一过程中会发生什么? int a[] = {1,2,3,4,5}; list<int> l(a, a+5); list<int>::iterator it = find(l.begin(), l.end(), 3); cout << *it << end; 先看看find函数的定义 template <class InputIterator, class T> InputIterator find(InputIterator first, InputIterator last, const T& value) { while (first != last && *first != value) ++first; return first; } 我们所调用的find函数的特化版本其实是: find<__list_iterator<int, int&, int*>, int>(__list_iterator<int, int&, int*> first, __list_iterator<int, int&, int*> last, const int& value) 从而find函数中所用到的!=、*、++等操作符都作用在__list_iterator<int, int&, int*>的身上,这正是泛型的作用所在。 STL中迭代器的各种特性 在STL的迭代器中同样用到了这种技巧,因为STL的迭代器在使用的时候需要了解各种迭代器的特性。主要特性包含以下几种: 1、iterator_category:表示迭代器所属的类型 2、value_type:表示迭代器所指数据的类型 3、difference_type:表示两个迭代器之间的距离类型 4、pointer:表示迭代器所指数据的指针类型 5、reference:表示迭代器所指数据的引用类型 通常迭代器的几种特性被放在iterator_traits中。 // 对所有Iterator的泛化 template <class Iterator> struct iterator_traits { 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; }; // 对指针类型的偏特化(Partial Spetialization) template <class T> struct iterator_traits<T*> { typedef random_access_iterator_tag iterator_category; // 指针类型是可以随机访问的 typedef T value_type; // 值类型 typedef ptrdiff_t difference_type; // 指针类型之间的距离一定是整型(ptrdiff_t被定义为int型) typedef T* pointer; typedef T& reference; }; template <class T> struct iterator_traits<const T*> { // 同上,只不过这里是常量指针 typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t difference_type; typedef const T* pointer; typedef const T& reference; }; 各种不同的迭代器的特性定义如下: // input iterator的属性 template <class T, class Distance> struct input_iterator { typedef input_iterator_tag iterator_category; typedef T value_type; typedef Distance difference_type; typedef T* pointer; typedef T& reference; }; // output iterator的属性 struct output_iterator { typedef output_iterator_tag iterator_category; typedef void value_type; typedef void difference_type; typedef void pointer; typedef void reference; }; // forward iterator的属性 template <class T, class Distance> struct forward_iterator { typedef forward_iterator_tag iterator_category; typedef T value_type; typedef Distance difference_type; typedef T* pointer; typedef T& reference; }; // bidirectional iterator的属性 template <class T, class Distance> struct bidirectional_iterator { typedef bidirectional_iterator_tag iterator_category; typedef T value_type; typedef Distance difference_type; typedef T* pointer; typedef T& reference; }; // random access iterator的属性 template <class T, class Distance> struct random_access_iterator { typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef Distance difference_type; typedef T* pointer; typedef T& reference; }; 通过iterator_traits就能得到相应interator的各种特性,这样可以让程序更灵活,也能提高效率。 下面几个例子是为了说明iterator_traits在STL中的使用 eg1. count模板函数,它的返回值必须使用difference_type template <class InputIterator, class T> typename iterator_traits<InputIterator>::difference_type count(InputIterator first, InputIterator last, const T& value) { typename iterator_traits<InputIterator>::difference_type n = 0; // 萃取迭代器的difference_type类型 for ( ; first != last; ++first) if (*first == value) ++n; return n; } eg2. advance模板函数,为了提高效率,必须针对不同类型的iterator重载不同的处理函数 template <class InputIterator, class Distance> inline void advance(InputIterator& i, Distance n) { __advance(i, n, iterator_category(i)); // 根据不同的类型调用不同的重载函数 } // iterator_category函数的定义 template <class Iterator> inline typename iterator_traits<Iterator>::iterator_category iterator_category(const Iterator&) { typedef typename iterator_traits<Iterator>::iterator_category category; // 其实就是返回Iterator的iterator_category类型 return category(); } 再看__advance函数针对不同迭代器的三种版本,它们分别针对input iterator、forward iterator、Bidirectional iterator和Random access iterator四种不同的迭代器 // 针对input iterator和forward iterator的版本 template <class InputIterator, class Distance> inline void __advance(InputIterator& i, Distance n, input_iterator_tag) { while (n--) ++i; // 只能单向移动 } // 针对Bidirectional iterator的版本 template <class BidirectionalIterator, class Distance> inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) { if (n >= 0) // 根据方向不同有不同的处理 while (n--) ++i; else while (n++) --i; } // 针对Random access iterator的版本 template <class RandomAccessIterator, class Distance> inline void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) { i += n; // 随机访问,提高效率 }
总的来说,在STL中是由容器(container)来负责设计适当的迭代器(iterator),由迭代器(iterator)来负责设计适当的迭代器属性。正因为这一点才使得容器和算法可以完全分离开来,通过迭代器提供的接口来访问容器的内部元素。在这里我们又一次看到了traits编程技巧的强大功能,在很大程度上弥补了C++语言不是强类型语言的不足之处。
iterator的概念源自于对遍历一个线性容器工具的抽象,即如何你能访问这个容器的某个元素。对于最简单的数组,当然可以用数组的索引值,因为数组是连续存放在内存中的;但对于链表,就必须用指针。除此之外,还有还有很多种数据结构需要提供一个方便的工具来访问其中的元素,方法有ID,关键字等等。为了统一所有的容器的这种工具的使用,一般提供一整套容器的开发者就会用一种方式来表示各种容器的访问工具。例如C++ STL就是使用iterator。MFC自己的容器使用position。C#和java也有自己的方法,但方法是不变的。iterator的用法可以被统一, 但不同的底层容器实现其iterator的原理是不一样的。例如iterator++你可以理解为移动到容器的下一个元素,如果底层如果是数组,把索引值加一就行;如果底层是链表,就得执行类似于m_pCurrent = m_pCurrent->pNext;的操作。因此每种容器都有自己的iterator实现方法。
C++ STL iterator的常用方法有:
iterator++ 移到下个元素
iterator-- 移到上个元素
*iterator 访问iterator所指元素的值]
< > == != iterator之间的比较,例如判断哪个元素在前
iterator1 + iterator2 iterator之间的加法运算,类似于指针加法
参考链接:http://blog.csdn.net/wind19/article/details/7489760
http://blog.chinaunix.net/uid-11618169-id-2865359.html
http://www.cppblog.com/skybook/archive/2008/04/23/47900.html