1、迭代器设计思维-STL关键所在
迭代器:就是提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素。
STL的中心思想在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一帖粘合剂将它们撮合在一起。只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。
以算法find( )为例,它接受2个迭代器和1个”搜寻目标”:
// 摘自SGI <stl_algo.h>
template <class InputIterator, class T>
InputIterator find(InputIterator first,InputIterator last,const T& value)
{
while (first != last && *first != value)
++first;
return first;
}
只要给予不同的迭代器,find()便能够对不同的容器进行查找操作:
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
const int arraySize = 7;
int ia[arraySize] = { 0,1,2,3,4,5,6 };
vector<int> ivect(ia, ia+arraySize);
list<int> ilist(ia, ia+arraySize);
deque<int> ideque(ia, ia+arraySize);
vector<int>::iterator it1 = find(ivect.begin(), ivect.end(), 4);
if (it1 == ivect.end())
cout << "4 not found." << endl;
else // 結果:4 found. 4
cout << "4 found. " << *it1 << endl;
list<int>::iterator it2 = find(ilist.begin(), ilist.end(), 6);
if (it2 == ilist.end())
cout << "6 not found." << endl;
else// 結果:6 found. 6
cout << "6 found. " << *it2 << endl;
deque<int>::iterator it3 = find(ideque.begin(), ideque.end(), 8);
if (it3 == ideque.end())
cout << "8 not found." << endl;
else// 結果:8 not found.
cout << "8 found. " << *it3 << endl;
return 0;
}
2、迭代器是一种智能指针
迭代器是一种行为类似指针的对象,而指针的各种行为中最常见也最重要的便是内容提领(dereference)和成员访问(member access),因此,迭代器最重要的编程工作就是对 operator* 和 operator-> 进行重载(overloading)工作。
使得所有实现细节得以封装起来,不被使用者看到,是每一种STL容器都提供有专属迭代器的缘故。
3、Traits编程技法——STL源代码门钥
迭代器所指对象的型别,称为迭代器的value type。
我们知道:
(1)通过函数模板的参数类型推导解决函数体内声明变量的问题
(2)进一步,函数模板的参数类型推导推而导之的只是参数,无法推导函数的返回值型别。这时,如果需要返回类型是迭代器所指对象的类型,解决办法就是内嵌类型声明,即在迭代器内部添加一种“特性”,通过这种“特性”,算法可以很容易地获知迭代器所指对象的类型。
但是,问题来了,并不是所有迭代器都是class type。原生指针是迭代器,但它不是一种类类型,无法定义内嵌类型。怎么办?针对原生指针做特殊化的处理,利用模板偏特化就可以做到了。
利用模板偏特化解决原生指针不能内嵌类型的问题,iterator_traits就是关键。
在STL实现中,traits编程技术利用了“内嵌类型”的编程技巧与C++的template参数推导功能,弥补了C++类型识别方面的不足。通过traits,算法可以原汁原味的将迭代器的属性萃取出来,帮助算法正确高效的运行。
所谓traits,其意义是,如果template 中的I定义有自己的value type,那么通过这个traits的作用,萃取出来的value_type就是I::value_type。traits就像一台”特性萃取机”,萃取出各个迭代器的特性。
4、迭代器基本框架
最常用到的迭代器相应型别有五类:value type, difference type, pointer, reference, iterator catagoly。
STL提供的iterator class如下。它不含任何成员,纯粹只是型别定义。
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会很原汁原味的榨取出来:
template <class I>
struct iterator_traits {
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
这些类型所表示的意义如下:
value type 表示迭代器所指对象的型别;
difference type 表示两个迭代器之间的距离;也可表示一个容器最大容量
reference 为引用类型;
pointer 为指针类型;
iterator_category 表明迭代器的类型;
根据迭代器移动特性与施行动作,迭代器被分为五类:
1、Input Iterator:这种迭代器所指对象,不允许外界改变,只读(read
only);
2、Output Iterator:唯写(write only);
3、Forward Iterator:允许「写入型」算法(例如 replace())在此种迭
代器所形成的区间上做读写动作;
4、Bidirectional Iterator:可双向移动。某些算法需要逆向走访某个迭
代器区间(例如逆向拷贝某范围内的元素),就可以使用 Bidirectional
Iterators;
5、Random Access Iterator:前四种迭代器都只供应一部份指标算术能力
(前3种支持 operator++ ,第4种再加上 operator–),第5种则涵盖所
有指标算术能力,包括 p+n, p-n, p[n], p1-p2, p1
迭代器类型
迭代器共分为五种,
分别为: Input iterator、Output iterator、Forward iterator、Bidirectional iterator、Random access iterator。
迭代器iterator 提供了一种一般化的方法对顺序或关联容器类型中的每个元素进行连续访问
例如,假设iter为任意容器类型的一个iterator,则++iter 表示向前移动迭代器使其指向容器的下一个元素,而*iter 返回iterator 指向元素的值,每种容器类型都提供一个begin()和一个end()成员函数。
begin()返回一个iterator 它指向容器的第一个元素
end()返回一个iterator 它指向容器的末元素的下一个位置
2.1 Input(输入)迭代器
只能一次一个向前读取元素,按此顺序一个个传回元素值。表2.1列出了Input迭代器的各种操作行为。Input迭代器只能读取元素一次,如果你复制Input迭代器,并使原Input迭代器与新产生的副本都向前读取,可能会遍历到不同的值。纯粹Input迭代器的一个典型例子就是“从标准输入装置(通常为键盘)读取数据”的迭代器。
表达式 功能表述
*iter 读取实际元素
iter->member 读取实际元素的成员(如果有的话)
++iter 向前步进(传回新位置)
iter++ 向前步进(传回旧位置)
iter1 == iter2 判断两个迭代器是否相同
iter1 != iter2 判断两个迭代器是否不相等
TYPE(iter) 复制迭代器(copy 构造函数)
2.2 Output(输出)迭代器
Output迭代器和Input迭代器相反,其作用是将元素值一个个写入。表2.2列出Output迭代器的有效操作。operator*只有在赋值语句的左手边才有效。Output迭代器无需比较(comparison)操作。你无法检验Output迭代器是否有效,或“写入动作”是否成功。你唯一可以做的就是写入、写入、再写入。
表达式 功能表述
*iter = value 将元素写入到迭代器所指位置
++iter 向前步进(传回新位置)
iter++ 向前步进(传回旧位置)
TYPE(iter) 复制迭代器(copy 构造函数)
2.3 Forward(前向)迭代器
Forward迭代器是Input迭代器与Output迭代器的结合,具有Input迭代器的全部功能和Output迭代器的大部分功能。表2.3总结了Forward迭代器的所有操作。Forward迭代器能多次指向同一群集中的同一元素,并能多次处理同一元素。
表达式 功能表述
*iter 存取实际元素
iter->member 存取实际元素的成员
++iter 向前步进(传回新位置)
iter++ 向前步进(传回旧位置)
iter1 == iter2 判断两个迭代器是否相同
iter1 != iter2 判断两个迭代器是否不相等
TYPE() 产生迭代器(default构造函数)
TYPE(iter) 复制迭代器(copy构造函数)
iter1 == iter2 复制
2.4 Bidirectional(双向)迭代器
Bidirectional(双向)迭代器在Forward迭代器的基础上增加了回头遍历的能力。换言之,它支持递减操作符,用以一步一步的后退操作。
2.5 Random Access(随机存取)迭代器
Random Access迭代器在Bidirectional迭代器的基础上再增加随机存取能力。因此它必须提供“迭代器算数运算”(和一般指针“指针算术运算”相当)。也就是说,它能加减某个偏移量、能处理距离(differences)问题,并运用诸如<和>的相互关系操作符进行比较。以下对象和型别支持Random Access迭代器:
可随机存取的容器(vector, deque)
strings(字符串,string,wstring)
一般array(指针)
迭代器相关辅助函数
3.1 advance() 令迭代器前进
3.2 distance() 处理迭代器之间的距离
3.3 iter_swap() 交换两个迭代器所指内容
了解了迭代器的类型,我们就能解释vector的迭代器和list迭代器的区别了。显然vector的迭代器具有所有指针算术运算能力,而list由于是双向链表,因此只有双向读写但不能随机访问元素。故vector的迭代器种类为Random Access Iterator,list 的迭代器种类为Bidirectional Iterator。我们只需要根据不同的迭代器种类,利用traits编程技巧萃取出迭代器种类,然后由C++的重载机制就能够对不同型别的迭代器采用不同的处理流程了。为此,对于每个迭代器都必须定义型别iterator_category,也就是上文代码中的typedef std::forward_iterator_tag iterator_category; 实际中可以直接继承STL中定义的iterator模板,模板后三个参数都有默认值,因此继承时只需要指定前两个模板参数即可。如下所示,STL定义了五个空类型作为迭代器的标签。