攻城狮凌风

迭代器失效

1.序列容器

           对于序列式容器(如vector,deque),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vetor,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。所以不能使用erase(iter++)的方式,还好erase方法可以返回下一个有效的iterator。

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

           为了防止迭代器失效,删除常用如下方法:

for (iter = container.begin(); iter != container.end(); )
    {
            if (*iter > 3)
              iter = container.erase(iter);    //erase的返回值是删除元素下一个元素的迭代器
            else{
                iter++;
            }
    }

      这样删除后iter指向的元素后,返回的是下一个有效元素的迭代器,这个迭代器是vector内存调整过后新的有效的迭代器。万无一失!

       以vector为例分析:引起内存重新分配的插入运算使所有迭代器失效,插入也使得插入位置及其后位置的迭代器失效,删除运算使得删除位置及其后位置的迭代器失效.

             vector的push_back操作 可能没事。但是一旦引发内存重分配,所有迭代器都会失效,此时所有存储位置被重新分配。
             vector的insert操作 插入点之后的所有迭代器失效;但一旦引发内存重分配,所有迭代器都会失效;但其返回前一个有效迭代器
            vector的erase操作 插入点之后的所有迭代器失效;返回后一个有效迭代器
            vector的reserve操作 所有迭代器失效(因为它导致内存重分配);

      对于deque:

          deque 的存储方式是“分块双向链表”的结构,块内是连续的线性表,块间是双向链表。形象点的说就是,deque是一个由指针组成的数组(当然,那不是数组,而是一小块连续空间,在deque中称这块连续空间为map),其中的指针再指向另一段(较大的)连续线性空间,称为缓冲区,缓冲区才是deque储存空间的主体,里面存放的是真正的元素。

           对于插入操作:

             插入工作引起deque内存重新分配的情况也会出现,但是那不是针对deque迭代器来说的,而对于deque中的map来说。deque只是将map的节点,从旧的空间复制到新的map中,但是其中作为储存实体的缓冲区没有重新分配,所以指向这些缓冲区的迭代器并没有失效。

             deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效,只会是那个元素所在的块的迭代器失效(这个失效的原因与 vector 类似),但是我们没办法那些块有多大,也就没办法判断到底哪些迭代器失效了,所以必须重新获取插入和删除操作会引起插入点之前或者之后的元素进行移动(调用copy函数)。除了首尾的插入(完全不失效)和删除(仅仅当前迭代器失效)。 

2.关联容器

         对于关联容器(如map, set, multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。整棵树也会调整,以符合红黑树或者二叉树的规范,但是单个节点在内存中的地址没有变化,变化的是各节点之间的指向关系。

         erase操作后,迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。

void mapTest()
{
    map<int, string> dataMap;
    for (int i = 0; i < 10; i++)
    {
          string strValue = "Hello, World";
          strValue += (char)(i+'0');
          dataMap.insert(make_pair(i, strValue));
    }

    cout<<"MAP元素内容为:"<<endl;
    map<int, string>::iterator iter=dataMap.begin(),temp=NULL;
    for (; iter != dataMap.end(); iter++)
	cout<<iter->second<<endl;

    cout<<"内容开始删除:"<<endl;
	int key;
    for (iter = dataMap.begin(); iter != dataMap.end(); )
    {
	key=iter->first;
         if (key% 2 == 0)
             dataMap.erase(iter++);//或者 temp=iter;iter++;erase(temp)
	 else{
	     cout<<iter->second<<endl;
             iter++;
	}
    }
}
       或者,使用iterator临时变量先保存当前iter,然后再++。即程序所用的erase(iter++)的实质。但是不能先erase(iter),再另外写一条iter++。

3.list

         list也存在迭代器失效的问题,不同的是,以上两种方式,都适合list. 因为对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种方法都可以使用。list实际使用链表存储数据。

         list适用于包含大量删除和插入操作的数据结构。



posted on 2015-08-04 10:20  攻城狮凌风  阅读(290)  评论(0编辑  收藏  举报

导航