一般来说,标准stl容器都提供了4中迭代器。对于一个容器container
而言,iterator
相当于 T*
,const_iterator
相当于 const T*
;我们以map
为例,其他的容器也大差不差的实现了下面三个函数的功能。
iterator insert(iterator position, const value_type& __x)
{
return _M_t.insert_unique(position, __x);
}
void erase(iterator __position)
{
_M_t.erase(__position);
}
void erase(iterator __first, iterator __last)
{
_M_t.erase(__first, __last);
}
上面三个函数的原型。基本能够看到,这几个函数的原型中都用了 iterator
迭代器。虽然容器提供了4种类型的迭代器,但使用最多的还是 iterator
类型的迭代器。
这也就是说,有些版本的stl容器的某些函数使用的都是 iterator
类型的迭代器。也就有了今天的主题,一般不确定该选择哪种迭代器的时候,选择iterator
类型的迭代器总是没错的。
看下下面的这张图,他能够很明显的体现上面4种迭代器的转换关系。
但stl提供了4种类型的迭代器,其他的迭代器就没有作用了吗?
比如当你选择从后向前遍历时,选择reverse_iterator
类型的迭代器是更好的选择。而如果在整个操作中并不会改变容器,只是遍历读取元素时,常量的迭代器并不是不能选择。
void insert(const_iterator __first, const_iterator __last)
{
_M_t.insert_unique(__first, __last);
}
上面函数也是容器map的成员函数,功能是向容器中插入另一个相同键值对类型容器中的一部分。
map<int, string> data{{1, "a"}, {2, "b"}, {3, "c"}, {4, "5"}};
map<int, string> data2;
data2.insert(++data.begin(), --data.end());
下面是一个iterator类型迭代器和const_iterator类型迭代器之间的混用问题。
map<int, int> m;
typedef map<int, int> IntMap;
typedef IntMap::iterator Iter;
typedef IntMap::const_iterator ConstIter;
Iter i;
ConstIter ci;
if(ci == i){} //能够正常编译
if(i - 3 > ci){} //代码可能编译错误
if(ci + 3 < i){} //并不能解决上述问题,据说有的版本可以结局上述的问题
以上,我们有足够的理由无条件的首先选用 iterator类型的迭代器。
在上面的例子图片中,我们看到iterator
类型迭代器转换为const_iterator
类型的迭代器时很容易的,但是反过来是无法转换的。其实,我们是有办法将const_iterator
类型的迭代器转换为 iterator
类型的迭代器的。
每当无路可走的时候,总会想起类型强制转换。强制类型转换就好像是最后的杀手锏。
map<int, int> m;
typedef map<int, int> IntMap;
typedef IntMap::iterator Iter;
typedef IntMap::const_iterator ConstIter;
ConstIter ci;
Iter i(ci);
Iter i(const_cast<Iter>(ci));
//[Error] invalid use of const_cast with type 'Iter {aka __gnu_cxx::__normal_iterator >}', which is not a pointer, reference, nor a pointer-to-data-member type
以上面的例子,都是编译不通过的,因为const_iterator
到iterator
之间是没有隐式转换的。
我们首先看个下面的例子,下面这个例子,首先有一个const_iterator
类型的迭代器,并且它指向了容器的最后一个元素。如果你想调用容器的一些只能用 iterator
类型迭代器的容器的成员方法,比如insert、erase
等。下面提供的方法可以安全地、可移植地将const_iterator类
型的迭代器转换为 iterator
类型的迭代器,并且不会涉及到任何的强制类型转换。
typedef vector<int> IntVec;
typedef IntVec::iterator Iter;
typedef IntVec::const_iterator ConstIter;
IntVec c;
c.push_back(3);
c.push_back(4);
c.push_back(5);
ConstIter cv;
cv = --c.end(); //有些容器的const_iterator 迭代器类型 cbegin() 和 cend()
Iter v(c.begin());
advance(v, distance<ConstIter>(v, cv));
cout << v; //4
上面的这种方法看似肺藏简单和直接。其原理如下:
下面是函数distance的声明。
template <class _InputIterator>
inline typename iterator_traits<_InputIterator>::difference_type
distance(_InputIterator __first, _InputIterator __last) {
typedef typename iterator_traits<_InputIterator>::iterator_category _Category;
__STL_REQUIRES(_InputIterator, _InputIterator);
return __distance(__first, __last, _Category());
}
由上面的函数声明我们可以看出,distance
函数的入参类型是一样的,但是我们上面调用的时候,给函数的参数类型却是两种类型。所以很多的编译器会直接报错,为了避免报错,我们需要指定类型。也就是说在调用distance
函数时,能够将我们代入函数的参数类型当作是一种类型来处理。
distance<ConstIter>(v, cv)
虽然有了可以方便转换的方法,那么我们一定要用这种方法来转换吗? 还是跟我们上面所说的,在使用容器的时候,尽量使用iterator
类型的迭代器代替const_iterator
类型和reverse_iterator
类型的迭代器。
上面这种转换的方式可以说是比较简单的,那效率如何?
我们看到上面这种转换的方式是将iterator迭代器和const_iterator有着相同的偏移量,那么也就很简单的,效率也就于使用的迭代器有关了。
如果是vector、deque、string等随机访问的迭代器,是一个常数时间的操作,但对于(双向迭代器)其他标准容器或者说哈希容器,他们的时间是线性的。而这种转换可能也会付出线性时间的代价。
调用reverse_iterator
迭代器的base()函数能够与之对应的iterator
类型的迭代器。我们先看一个例子。
#include
#include
using namespace std;
int main()
{
typedef vector<int> IntVec;
typedef IntVec::iterator Iter;
typedef IntVec::reverse_iterator ReverserIter;
IntVec vec;
vec.reserve(5);
for(int index = 1; index <= 5; ++index)
{
vec.push_back(index);
}
ReverserIter ri = ++vec.rbegin();
Iter i(ri .base());
cout << *ri << " " << *i; // 4 5
return 0;
}
通过上面的例子我们可以明显的看出,reserve_iterator
通过base()
函数转换之后的iterator
的迭代器指向的是其前一个。这是因为,reserve_iterator
是从右往左遍历的,而iterator
是从左往右遍历的。通过下面的一张图就能很明确的表示。
所以,如果我们需要在ri 处插入元素时,都是将元素插入到迭代器的前面,对于ri
和i
来说,他们俩的前面指向的内存空间是一样的,直接调用 ri.base()
就可以,对插入而言,ri和i是等价的,ri.base()
是真正和 ri对应的iterator
。
对容器的操作不仅仅只有插入操作,还有删除元素也会改变容器的内存顺序。那么我们看一看删除操作。还是从上面的例子修改。
vec.earse(ri.base());
如果直接调用上面的代码,会不会如愿删除迭代器 ri
所指的元素呢?从上面的例子中,我们可以看出来是不能直接删除的,因为直接调用base()
方法,转换之后的迭代器指向的内存是 ri
的前一个内存空间。所以我们能需要对转换后的迭代器做自减处理。
vec.earse(--ri.base());
上面的操作时直接对转换后的直接进行了自减;但是对于标准容器 vector和string
,他们很多实现并不能编译通过。因为 vector 和 string
中对 iterator
和const_iterator
的实现是内置指针。所以 ri.base()
的返回结果是一个指针。
而 C和C++都规定了从函数返回的指针不应该被修改 。
所以,如果要正常的删除容器中的元素,我们可以先对 ri 进行自增,然后通过base()
转换与之对应的iterator迭代器。
vec.earse((++ri).base());