stl容器使用中的经验(七)--iterator 优于 const_iterator、reverse_iterator、const_reverse_iterator

1、iterator 优于 const_iterator、reverse_iterator、const_reverse_iterator

一般来说,标准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容器使用中的经验(七)--iterator 优于 const_iterator、reverse_iterator、const_reverse_iterator_第1张图片

但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类型的迭代器。

2、const_iterator 转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_iteratoriterator之间是没有隐式转换的。

我们首先看个下面的例子,下面这个例子,首先有一个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

上面的这种方法看似肺藏简单和直接。其原理如下:

  1. 首先我们知道const_iterator类型迭代器的位置
  2. 新建立一个iterator类型的迭代器,使其指向容器的开始位置
  3. 使用distance函数计算出两个迭代器之间的偏移量
  4. 使用advance函数将iterator类型迭代器移动相同的偏移量

下面是函数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等随机访问的迭代器,是一个常数时间的操作,但对于(双向迭代器)其他标准容器或者说哈希容器,他们的时间是线性的。而这种转换可能也会付出线性时间的代价。

3、reverse_iterator迭代器base()函数产生的iterator迭代器

调用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是从左往右遍历的。通过下面的一张图就能很明确的表示。

stl容器使用中的经验(七)--iterator 优于 const_iterator、reverse_iterator、const_reverse_iterator_第2张图片

所以,如果我们需要在ri 处插入元素时,都是将元素插入到迭代器的前面,对于rii来说,他们俩的前面指向的内存空间是一样的,直接调用 ri.base() 就可以,对插入而言,ri和i是等价的,ri.base()是真正和 ri对应的iterator

stl容器使用中的经验(七)--iterator 优于 const_iterator、reverse_iterator、const_reverse_iterator_第3张图片

对容器的操作不仅仅只有插入操作,还有删除元素也会改变容器的内存顺序。那么我们看一看删除操作。还是从上面的例子修改。

vec.earse(ri.base());

如果直接调用上面的代码,会不会如愿删除迭代器 ri 所指的元素呢?从上面的例子中,我们可以看出来是不能直接删除的,因为直接调用base()方法,转换之后的迭代器指向的内存是 ri 的前一个内存空间。所以我们能需要对转换后的迭代器做自减处理。

vec.earse(--ri.base());

上面的操作时直接对转换后的直接进行了自减;但是对于标准容器 vector和string,他们很多实现并不能编译通过。因为 vector 和 string 中对 iteratorconst_iterator的实现是内置指针。所以 ri.base() 的返回结果是一个指针。

而 C和C++都规定了从函数返回的指针不应该被修改 。

所以,如果要正常的删除容器中的元素,我们可以先对 ri 进行自增,然后通过base()转换与之对应的iterator迭代器。

vec.earse((++ri).base());

你可能感兴趣的:(#,STL标准模版库,c++,stl,迭代器,迭代器之间转换,迭代器选择)