深入理解迭代器


迭代器是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物,其中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"
}
}

总结

设计适当的相应型别,是迭代器的责任,设计适当的迭代器,则是容器的责任,唯容器本身,才知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值