前言
最近在看《STL源码剖析》,并且也跟着在实现algorithm.h
这个头文件。里面的算法基本都是对迭代器所指向的容器区间内的元素做操作,因此这些算法的泛型模板参数基本都有迭代器类型。针对不同的算法,对迭代器类型的要求也不同,本篇博客我就简单介绍一下C++中的五种迭代器类别。
五种迭代器类别的关系
首先介绍五种迭代器类别:
- 输入迭代器
input iterator
- 输出迭代器
output iterator
- 前向迭代器
forward iterator
- 双向迭代器
bidirectional iterator
- 随机访问迭代器
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;
}
};