正如大多C++编程人员所知的,每个标准容器类都提供四种迭代器类型。对于container<T>而言,iterator的作用相当于T*,而const_iterator则相当于const T*(与T const* 意思一样)。增加一个iterator或者const_iterator可以在一个从容器开头向尾部的遍历中让你移动到容器的下一个元素。reverse_iterator与const_reverse_iterator同样相当于对应的T*和const T*,所不同的是,增加reverse_iterator或者const_reverse_iterator会在从尾到头的遍历中让你移动到容器的下一个元素。


首先让我演示两个东西。第一,看看vector<T>的insert和erase的样式:

iterator insert(iterator position, const T& x);

iterator erase(iterator position);

iterator erase(iterator rangeBegin, iterator rangeEnd);

每个标准容器都包含了和这差不多的函数,虽然返回类型因容器类型的不同而不同。需要注意的是:这些方法只接受iterator类型的参数,而不是const_iterator、reverse_iterator或const_reverse_iterator。虽然容器类支持四种迭代器类型,但其中的一种类型有着其他所没有的特权,那就是iterator。

我要让你看的第二个东西是这张图,它显示了几种迭代器之间存在的转换关系:


图中显示了从iterator到const_iterator、从iterator到reverse_iterator和从reverse_iterator到const_reverse_iterator可以进行隐式转换。并且,reverse_iterator可以通过调用其base成员函数转换为iterator。const_reverse_iterator也可以类似地通过base转换成为const_iterator。一个图中无法显示的事实是:通过base得到的也许并非你所期待的iterator。

让我们再进一步了解iterator、const_iterator、const iterator三者之间的区别,通过下面的代码段来说明:

vector<int> iVec;

vector<int>::iterator iter0 = iVec.begin();

vector<int>::const_iterator iter1 = iVec.begin(); //常量迭代器

const vector<int>::iterator iter2 = iVec.begin();  //常量迭代型

*iter0 = 1;  //right

*iter1 = 1;  //error 不能用const_iterator改变其中元素

*iter2 = 1;  //right

++iter0;     //right

++iter1;     //right 改变迭代器,而不是其中的元素

++iter2;     //error

//总而言之:常量迭代器不同于常量迭代型。这意味着你能够改变常量迭代器指向的位置(比如做自加++)。然而,你不能改变常量迭代器所指向的对象的值。


你应该发现了没有办法从一个const_iterator转换得到一个iterator,也无法从const_reverse_iterator得到reverse_iterator。这一点非常重要,因为这意味着如果你有一个const_iterator或者const_reverse_iterator,你会发现很难让它们和容器的一些成员函数合作。那些成员函数要求iterator,而你无法从const迭代器类型反过来得到iterator。当你需要指出插入位置或删除的元素时,const迭代器几乎没有用

但是,千万不要傻乎乎的宣称const迭代器一无是处。不,它们可以与算法默契配合,因为算法通常并不关心迭代器是什么类型,只要是适当的种类就可以了,很多容器的成员方法也接受const迭代器。只有insert和erase的一些形式有些吹毛求疵。


从const_iterator 到iterator

typedef deque<int> IntDeque;    

typedef IntDeque::iterator Iter;

typedef IntDeque::const_iterator ConstIter;

ConstIter ci;          // ci是const_iterator

...

Iter i(ci);                      // 错误!没有从const_iterator到iterator隐式转换的途径

Iter i(const_cast<Iter>(ci));    // 仍是个错误!不能从const_iterator映射为iterator!


事实表明,把const迭代器映射为迭代器是病态的。

我们并不会就这样束手无策,有一种安全的、可移植的方法获取它所对应的iterator[,而且,用不着陷入类型系统的转换。下面是解决思路的本质,虽然在它编译前还要稍作修改:

typedef deque<int> IntDeque;         // 和以前一样

typedef IntDeque::iterator Iter;

typedef IntDeque::const_iterator ConstIter;

IntDeque  d;

ConstIter ci;

...  // 让ci指向d

Iter i(d.begin());          // 初始化i为d.begin()

// 把i移到指向ci位置(但请留意下面关于为什么在它编译前要调整的原因)

advance(i, distance<ConstIter>(i, ci));

这种方法看上去非常简单,直截了当,也很让人吃惊吧。要得到与const_iterator指向同一位置的iterator,首先将iterator指向容器的起始位置,然后把它向前移到和const_iterator距离容器起始位置的偏移量一样的位置即可!这个任务得到了两个函数模板advancedistance的帮助,它们都在<iterator>中声明。distance返回两个指向同一个容器的iterator之间的距离;advance则用于将一个iterator移动指定的距离。如果ici指向同一个容器,那么表达式advance(i, distance(i, ci))会将i移动到与ci相同的位置上。

如果这段代码能够通过编译,它就能完成这种转换任务。但似乎事情并不那么顺利。想知道为什么,先来看看distance的定义:

template<typename InputIterator>

typename iterator_traits<InputIterator>::difference_type

distance(InputIterator first, InputIterator last);

不要被这个函数的长达个字符的返回类型卡住,也不用理会difference_type是什么东西。取而代之的是,把注意力集中在参数的类型InputIterator

当遇到distance调用时,你的编译器需要根据使用的实参类型推断出InputIterator的类型。再来看看我所说的不太正确的distance调用(如下):

advance(i, distance(i, ci));              // 调整i,指向ci位置

上面的调用有两个参数传递给distance,i和ci。i的类型是Iter,即deque<int>::iterator的typedef。对编译器来说,这表明调用distance的InputIterator是deque<int>::iterator。但ci是ConstIter,即deque<int>::const_iterator的typedef。这表明那个InputIterator是deque<int>::const_iterator。InputIterator不可能同时有两种不同的类型,所以调用distance失败。一般会造成一些冗长的出错信息,可能会也可能不会说明是编译器无法得出InputIterator是什么类型。

要顺利地调用distance,你需要排除歧义。最简单的办法就是显式的指明distance调用的模板参数类型,从而避免编译器自己得出它们的类型:

advance(i, distance<ConstIter>(i, ci));

我们现在知道了怎么通过advancedistance获取const_iterator相应的iterator了。但另一个我们现在一直避开却很值的考虑的实际问题是:这个技巧的效率如何?答案很简单。取决于你所转换的究竟是什么样的迭代器。对于随机访问的迭代器(比如vectorstringdeque的)而言,这是常数时间的操作。对于双向迭代器(也就是,所有其它容器和包括散列容器的一些实现)而言,这是线性时间的操作。


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐