深入理解迭代器
迭代器是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物,其中iterater模式定义如下:提供一种方法,使之能够依序巡访某个聚合物所含的各个元素,而又无需暴露该聚合物的内部表述方法。
迭代器设计思维
不论是泛型思维或STL的实际运用,迭代器都扮演着重要的角色,STL的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶着剂将他们聚合在一起
以find()函数为例,它接受两个迭代器和一个"搜寻目标"
const int arraySize=7;
int ia[arraySize] ={0,1,2,3,4,5,6};
vector<int> ivect(ia,ia+arraySize);
vector<int>::iterator it1=find(ivect.begin(),ivect.end(),4);
if(it1==ivect.end())
cout<<"4 not found"<<endl;
else
cout<<"4 found"<<*it1<<endl;
迭代器是一种行为类似指针的对象
指针的最常用也最重要的编程工作就是对operator*和operator->进行重载工作,比如:
template<class T>
class ptr
{
T& operator*() const
{
return *pointee;
}
T* operator->() const
{
return pointee;
}
private:
T *pointee;
};
迭代器相应型别
假设算法中有必要声明一个变量,以迭代器所指对象的型别为型别,如何实现?
解决办法是:利用function template的参数推导机制,例如:
template <class I,class T>
void func_impl(I iter,T t)
{
T tmp;
//这里做原本func()应该做的全部工作
};
template <class I>
inline void func(I iter)
{
func_impl(iter,*iter);
}
int main()
{
int i;
func(&i);
}
我们以func()为对外接口,却把实际操作全部置于func_imlp()之中,由于func_impl()是一个function template,一旦被调用,编译器会自动进行template参数推导,于是导出型别T
迭代器相应类型不只有"迭代器所指对象的型别"一种而已,最常用的相应型别有五种,并非任何一种都可利用上述的template参数推导机制来取得,我们需要更全面的解法
Traits编程技法
迭代器所指对象的型别,称为该迭代器的value type,假如value type必须用于函数的返回值,就无法采用上面的方法了,函数的"template参数推导机制”
推而导之的只是参数,无法推导函数的返回值型别
我们可以采用如下的方法:
template <class T>
struct MyIter
{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){ }
T& operator*() const { return *ptr;}
};
template <class T>
typename I::value_type//func的返回值型别
func(I ite)
{ return *ite;}
MyIter<int> ite(new int(8));
cout<<func(ite);
采用此方法有一个限制条件:迭代器必须是class type,假如是一个原始指针,就无法为它定义内嵌型别,我们可以通过偏特化来解决这些问题
偏特化的意义
面对以下这么一个class template
template<typename T>
class C { };//这个泛化版本允许T为任何型别
我们很容易接受它有形式如下的偏特化版本
template<typename T>
class C<T*> { };
有了偏特化,我们就可以设计特别版本的Traits,比如:
template<class I>
struct iterator_traits<T*>
{
typedef T value_type;
};
针对指向常数对象的指针,如下例子:
iterator_traits<const int*>::value_type;
获得到的是const int而不是int,声明一个无法赋值的暂时变量,没什么用,为此我们需要设计一个偏特化版本
template<class T>
struct iterator_traints<const T*>//偏特化版——当迭代器是个pointer——to——const时
{
typedef T value_type;//萃取出来的型别应该是T而非const T
};
迭代器相应类别之一:value_type
所谓value_type,是指迭代器所指对象的型别,任何一个打算与STL算法有完美搭配的class,都应该定义自己的value type内嵌型别
迭代器相应类型之二:difference_type
difference type用来表示两个迭代器之间的距离,因此它也用来表示一个容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量,如果一个泛型算法提供计数功能,例如STL的count(),其返回值必须使用迭代器的difference type:
template<class I,class T>
typename iterator_traits<I>::difference_type
count(I first,I last,const T& value)
{
typename iterator_traits<I>::difference_type n=0;
for( ;first!=last;++first)
{
if(*first==value)
{
++n;
}
return n;
}
}
针对原生指针的特化版本,以C++内建的ptrdiff_t(一般用于保存两个指针相减的结果)作为原生指针的difference type
template <class I>
struct iterator_traits
{
typedef typename I::difference_type difference_type;
};
template <class T>
struct ierator_traits<T*>
{
typedef ptrdiff_t difference_type;
};
template <class T>
struct iterator_traits<const T*>
{
typedef ptrdiff_t difference_type;
};
迭代器相应型别之三:reference_type
从迭代器所指内容是否发生改变的角度,迭代器分为两种:不允许改变称为constant iterator,例如const int* pic,允许改变的,称为mutable itrator,例如int * pi
在C++中,函数如果要传回左值,都是以by reference的方式进行的,所以当p是个mutable itrerator时,如果其value_type是T,那么* p的型别不应该是T,应该是T&,这里所讨论的*p的型别,即所谓的reference type;
迭代器相应型别之四:pointer_type
我们能够传回一个pointer,指向迭代器所指之物:
Item& operator*() const { return *ptr;}
Item* operator->() const { return ptr;}
Item& 便是reference_type,Item*便是pointer_type
迭代器相应型别之五:iterator_category
根据移动特性与施行操作,迭代器被分为五类:
- Input iterator:这种迭代器所指的对象,不允许外界改变,只读
- Output iterator:唯写
- Forward iterator:允许写入型算法,例如replace(),在此种迭代器所形成的区间上进行读写操作
- Bidirectional iterator:可双向移动,某些算法需要逆向走访某个迭代器区间
- Random Access iterator:覆盖所有指针的算法能力
以advance()为例
我们希望能实现在编译期就选择正确的版本,重载函数机制可以达到这个目标
设计考虑如下:如果traita有能力萃取出迭代器的种类,我们便可以利用这个迭代器类型相应型别作为advanced()的第三参数,这个相应型别必须是一个class type,因为编译器需要依赖它进行重载决议
下面五个class,代表5种迭代器型别:
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_itreator_tag:public bidirectional_iterator_tag{ };
template <class InputIterator,class Distance>
inline void _advance(InputIterator i,Distance n,input_iterator_tag)
{
while(n--) ++i;
}
//这是一个单纯的传递调用
template <class ForwardIterator,class Distance>
inline void _advance(ForwardIterator& i,Distance n,forward_iteartor_tag)
{
advance(i,n,input_iterator_tag);
}
每一个_advance()的最后一个参数都只声明型别,并未指定参数名称,因为它纯粹只是用来激活重载机制
还需要设计一个对外开放的上层控制接口,调用各个重载的_advance(),这一上层接口只需两个参数,当它准备将工作转给上述的_advance()时,才自行加上第三参数
template <class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n)
{
_advance(i,n,iterator_traits<InputIterator>::iterator_category());
}
消除单纯传递调用的函数
以class来定义迭代器的各种分类标签,不仅可以促进重载函数的成功运作,另一个好处是,通过继承,我们不必写单纯只做传递调用的函数,比如下面的例子:
#include<iostream>
using namespace std;
struct B { };
struct D1:public B { };
struct D2:public D1 { };
template <class I>
func(I& p,B)
{
cout<<"B version"<<endl;
}
template <class I>
func(I& p,D2)
{
cout<<"D2 version"<<endl;
}
int main()
{
int *p;
func(p,B());//参数与参数完全吻合,输出:"B version"
func(p,D1());//参数与参数不吻合,因继承关系而自动传递调用,输出:"B version"
func(p,D2());//参数与参数完全吻合,输出:"D2 versin"
}
}
总结
设计适当的相应型别,是迭代器的责任,设计适当的迭代器,则是容器的责任,唯容器本身,才知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为