C++ STL迭代器相关

map 迭代器

  1. 以下代码在编译时出错,提示c++map报错 map/set iterators incompatible
map<string, int>::iterator it = GetFuncMap().begin();
while (it != GetFuncMap().end())
{
    //.......处理一些事情
    ite++;
}

原因:直接调用两次GetFuncMap函数来遍历,却忽略啦每次调用函数的时候返回的是两个内容相同的map副本,但是它们的迭代器类型是不一样的,即it != GetFuncMap().end()中it和GetFuncMap().end()不是同一个map的迭代器,所以就报错。
修改后的代码:

map<string, int> tmap = GetFuncMap();
map<string, int>::iterator it = tmap.begin();
while (it != tmap.end())
{
    //...
    ite++;
}

相同的示例:

class Solution {
public:
	vector<vector<int>> subsetsWithDup(vector<int>& nums) {
		vector<vector<int>>res;
		map<int, int>mnum;
		for (int i : nums)
			++mnum[i];
		for (int i = 0; i <=nums.size(); i++)
		{
			vector<int> tmp;
			dfs(mnum,mnum.begin(), i, res, tmp);
		}
		return res;
	}
	void dfs(map<int,int>&nums,map<int,int>::iterator now, int k, vector<vector<int>>&res, vector<int> tmp) {
		
		if (tmp.size() == k)
		{
			res.push_back(tmp);
			return;
		}
		for (auto i = now; i !=nums.end() ; i++) {
			if (i->second == 0)
				continue;
			tmp.push_back(i->first);
			(i->second)--;
			dfs(nums, i,k, res, tmp);
			tmp.pop_back();
			(i->second)++;
		}
	}
};

分析:dfs函数中的第一个参数要为map的引用形式,否则在dfs中传递的是map的一个副本,若是副本的话在dfs函数中的for循环的i !=nums.end()语句有incompatible的错误,原因迭代器i和nums.end()不是同一个map的迭代器。


迭代器失效(erase时)

  • 对于序列式容器(如vector,deque),序列式容器就是数组式容器,删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。所以不能使用erase(iter++)的方式,还好erase方法可以返回下一个有效的iterator。
正确:
for (iter = cont.begin(); iter != cont.end();)
{
   if (shouldDelete(*iter))
      iter = cont.erase(iter);  //erase删除元素,返回下一个迭代器
   else
      ++iter;
 }
错误:报错vector iterator not incrementable.
 for (iter = container.begin(); iter != container.end(); iter++)
 {
            if (*iter > 3)
              container.erase(iter);
 }

分析:迭代器在执行++操作时报错!已经失效的迭代器不能再进行自增运算了。对于序列式容器,比如vector,删除当前的iterator会使后面所有元素的iterator都失效。这是因为顺序容器内存是连续分配(分配一个数组作为内存),删除一个元素导致后面所有的元素会向前移动一个位置。(删除了一个元素,该元素后面的所有元素都要挪位置,所以,iter++,已经指向的是未知内存)。但是序列式容器的erase方法可以返回下一个有效的iterator。

总结: vector是一个顺序容器,在内存中是一块连续的内存,当删除一个元素后,内存中的数据会发生移动,以保证数据的紧凑。所以删除一个数据后,其他数据的地址发生了变化,之前获取的迭代器根据原有的信息就访问不到正确的数据。

为了防止vector迭代器失效,常用如下方法:
for (iter = container.begin(); iter != container.end(); )
{
     if (*iter > 3)
         iter = container.erase(iter);    //erase的返回值是删除元素下一个元素的迭代器
     else
           iter++;
 } 

  • 对于关联容器(如map, set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。
正确:
for (iter = cont.begin(); it != cont.end();)
{
   if (shouldDelete(*iter))
      cont.erase(iter++);
   else
      ++iter;
}
错误:
for (iter = cont.begin(); it != cont.end();it++)
{
   if (shouldDelete(*iter))
      cont.erase(iter);
}

分析:cont.erase(iter)之后,iter就已经失效了,所以iter无法自增,即iter++就会报错Debug Assertion Failed。解决方案,就是在iter失效之前,先自增。
解析:先让iter指向下一个有效的节点,但是返回给erase函数的是原来的iter副本。map.erase(iter++);这句话分三步走,先把iter传值到erase里面,然后iter自增,然后执行erase,所以iter在失效前已经自增了。map是关联容器,以红黑树或者平衡二叉树组织数据,虽然删除了一个元素,整棵树也会调整,以符合红黑树或者二叉树的规范,但是单个节点在内存中的地址没有变化,变化的是各节点之间的指向关系。


总结:迭代器失效分三种情况考虑,也是非三种数据结构考虑,分别为数组型,链表型,树型数据结构。

数组型数据结构:(vector、deque) 该数据结构的元素是分配在连续的内存中,insert和erase操作,都会使得删除点和插入点之后的元素挪位置,所以,插入点和删除掉之后的迭代器全部失效,也就是说insert(*iter)(或erase(*iter)),然后在iter++,是没有意义的。解决方法:erase(*iter)的返回值是下一个有效迭代器的值。 iter =cont.erase(iter);

链表型数据结构: (list) 对于list型的数据结构,使用了不连续分配的内存,删除运算使指向删除位置的迭代器失效,但是不会失效其它迭代器.解决办法两种,erase(*iter)会返回下一个有效迭代器的值,或者erase(iter++).

树形数据结构: (map、set) 使用红黑树来存储数据,插入不会使得任何迭代器失效;删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器.erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。

注意: 经过erase(iter)之后的迭代器完全失效,该迭代器iter不能参与任何运算,包括iter++,*iter。

扫描二维码关注公众号,回复: 9337527 查看本文章
发布了76 篇原创文章 · 获赞 6 · 访问量 2777

猜你喜欢

转载自blog.csdn.net/u014618114/article/details/104158570