这两天,使用std::map的iterator的时候,遇到了很多的问题,最后发现很多想当然的想法是错误的。
比如:list的iterator自减到了begin()之后,再自减的话会回到end(),从而形成一个循环队列。
而map的iterator如果一直自减到begin()之后,继续自减的话,iterator会始终指向begin();end()之后自增的话,也不会有任何改变。
其实上述行为还与stl的实现有关,比如stlport的map,end()之后自增,iterator不会始终指向end()。所以我们可以只能认为iterator在begin()之后--、或者end()之后++,这两种行为的效果是未定义的,使用的时候必须非常小心。
以下,针对vc和stlport的stl实现,就各种容器的iterator作一个总结:
1. list: vc和stlport的实现是一致的,即循环队列。begin()-- = end(),end()++ = begin()。list::iterator实际上存储的是指向元素的指针。
2. vector: 在vc和stlport中,vector都使用整数作为iterator,所以begin()--,end()++如果反引用的话都是对内存的违规访问。
3. map/set: map和set本质上都是使用一个treebase的结构。实际上set就是只有key的map。如前所述vc中begin()--,以及end()++都不会有任何操作。但是stlport中确对end()++有特殊处理(注意以下代码中的check special case),由于暂时还没没有研究map中使用的树的操作方式(这得复习数据结构的知识了,以后有空再说吧),所以对下面这段代码的意思还没搞清楚。不过从效果上来看,end()++是变化的。
template <class _Dummy> _Rb_tree_node_base* _STLP_CALL
_Rb_global<_Dummy>::_M_increment(_Rb_tree_node_base* _M_node) {
if (_M_node->_M_right != 0) {
_M_node = _Rb_tree_node_base::_S_minimum(_M_node->_M_right);
}
else {
_Base_ptr __y = _M_node->_M_parent;
while (_M_node == __y->_M_right) {
_M_node = __y;
__y = __y->_M_parent;
}
// check special case: This is necessary if _M_node is the
// _M_head and the tree contains only a single node __y. In
// that case parent, left and right all point to __y!
if (_M_node->_M_right != __y)
_M_node = __y;
}
return _M_node;
}
总结一下:做任何事情,动手之前都必须深思熟虑。对于可能存在错误的地方,必须检查清楚,否则等到代码写完之后再回头来检查,那将是非常困难的事情。
这两天使用map时,写代码的时候已经意识到iterator的begin()--和end()++可能有问题,但是当时由于急于求成,没有停下来好好研究一下iterator,而是想着等出了问题再回头来研究(这时实际上抱着一种侥幸心理,如果没出问题的话,那就不用花时间研究iterator了)。结果果然出了问题,而出了问题的时候早已把begin()--和end()++忘掉了,这时候必须通过设断点,单步运行等手段一步一步检查可能出现的错误,花了半天才想起begin()--的问题。由此看来做事千万不要有侥幸心理,否则因小失大、得不偿失!