c++之迭代器失效

1.首先从一到题目开始谈说起迭代器失效。有时我们很自然并且自信地 用下面方法删除vector元素:

#include <iostream>#include <stdio.h>#include <vector>#include <algorithm>#include <string>

void del_elem(vector<string> &vec, const char * elem)
{
    vector<string>::iterator itor = vec.begin();
    for (; itor != vec.end(); itor++)
    {
        if (*itor == elem)
        {
            vec.erase(itor);
        }
    }
}

template <class InputIterator>
void show_vec(InputIterator first, InputIterator last)
{
    while(first != last)
    {
        std::cout << *first << " ";
        first++;
    }

    std::cout << " " << std::endl;
}

int
main(void)
{
    string arr[] = {"php", "c#", "java", "js", "lua"};
    vector<string> vec(arr, arr+(sizeof(arr)/sizeof(arr[0])));

    std::cout << "before del: " << std::endl;
    show_vec(vec.begin(), vec.end());
    del_elem(vec, "php");
    std::cout << "after del: " << std::endl;
    show_vec(vec.begin(), vec.end());

    return 0;
}

  当 string arr[] = {"php", "c#", "java", "js", "lua"}; 时,运行上边程序,得到如下输出:

    

  运行结果是正确的啊。 找到 "php" ,然后删除,剩下四个元素。 

  但是实际上 del_elem 的过程是和我们想象的不一样的,在 del_elem中打印下每一步的 itor 的值,就会发现蛛丝马迹。

  将 del_elem加上log:

  

void del_elem(vector<string> &vec, const char * elem)
{
    std::cout << "----------------------------" << std::endl;

    vector<string>::iterator itor = vec.begin();
    for (; itor != vec.end(); itor++)
    {
        std::cout << *itor << std::endl;
        if (*itor == elem)
        {
            vec.erase(itor);
        }
    }

    std::cout << "----------------------------" << std::endl;
}

  我们在做删除操作前,打印每个元素的值, 继续编译运行得到如下结果:

    

  在做 del_elem操作时,少打印了一个 "c#", 也就是在打印完"php",然后删除php以后,接下来打印的不是 "c#", 而直接打印了 "java" 。

那么我们可以将 vec.erase(itor) 注释掉,然后 可以得到 del_elem 会打印所有的元素值,

  如此看来 c# 是因为执行了erase 操作以后,“变没了”。

  弄清这个问题,我们要看看一组vector操作的定义:

iterator erase(iterator position)
{
    if(position + 1 != end())
        copy(position + 1, finish, position);
    --finish;
    destroy(finish);
    return position;
}

iterator begin() { return start; }

iterator end() { return finish; }

  我们经常使用  vec.begin(), vec.end(), 想必也能知道start和finish 为何物。

  首先看erase函数: 先判断 待删除的迭代器是否为 finish 的前一个元素,也就是vector中的最后一个元素,

  如果不是最后一个元素,就将待删除迭代器的后边所有元素往前移动一个slot, 然后 --finish  重新定位finish指针。

    此时finish指针指向的是没删除之前的最后一个元素地址,本例中就是 lua的地址, 但是当前的finish指针处的值已经没用了,于是调用destroy。

    如果待删除迭代器是finish的前一个元素的话,那么就直接移动finish指针,调用destroy销毁该位置处的元素对象。

    与此同时,我们看到erase函数传进来的迭代器,只起到了一个位置定位判断的作用,erase本身没有对该迭代器有任何操作,该迭代器的值所在地址仍然有效,但是由于进行了copy操作,position处的值已经变成了"c#".

    再回过头来看一下我们的 del_elem 函数:

      当删除第一个元素php之后,在执行 itor++之前,php之后的所有元素都前移了一个slot导致此时 itor存放的元素已经是 c#,

      于是继续执行itor++后,此时itor又向后移动,itor中的值已经是java,c#就是这样被漏掉的。

  1-2 由此,又可以得出另一个结论,当arr中有n(n>=2)个 连续的php元素,我们用 del_elem函数 是不能删除掉所有的php元素的,于是这样就会导致bug。

  我们将 string arr[] = {"php", "c#", "java", "js", "lua"}; 改为 string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "lua"}; 后,观察运行结果:

    

  固然不出所料,php没有被删除干净,因为当删除第一个php以后,用当前 del_elem 方法,总是会漏掉删除的php之后的元素,如果这个元素恰好是 "php",便会出现bug。

  1-3 用当前 del_elem删除一个元素,会导致 finish 前移一个slot,如果将php放到最后slot,即finish之前的slot中,当删除最后一个php后,finish会指向删除的php的地址(已经非法了),

    然后php的地方会被销毁,然后又执行 itor++,于是此时的itor指向到finish的后边,当 判断 itor != vec.end() 时,这个式子是成立的,于是继续执行,但是当对迭代器解引用时,最终会由于是非法

    引用地址,程序崩掉。我们来看一下是否是这样, 将最后一个元素改为 "php"; string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "php"};

    编译运行,结果如下:

    

    gdb调试,发现是因为 *itor 导致程序崩溃。

2 以上例子指出了vector 删除元素时选择的方法不当导致的一些问题;

  1 是删除多个相同元素时,因为vector自身的特性导致 删除不净,出现bug

  2 是当删除的元素时最后一个元素,可能导致程序崩溃。

3 我们很多时候都知道vector 迭代器失效会出问题,但是很多时候不知道会导致什么问题。

   以上例子列举了 迭代器失效的 结果, 那么反过来 ,我们再研究 “什么是vector删除元素会导致迭代器失效” 的问题。  

   我的结论是,在对vector进行删除元素的时候, 删除元素之前,假设我们定义了一些迭代器分别指向,

    1 迭代器的位置位于 待删除迭代器之前,

    2 待删除的迭代器

    3 迭代器的位置位于 待删除的迭代器之后

   那么当对待删除的迭代器调用erase(itor)以后,之前定义在itor之前的迭代器依旧有用, 之前定义的 itor 以及 itor之后的迭代器 已经失效了,这里的失效是指,这些迭代器所指的元素内容已经和删除之前的不一样了,甚至可能是指向了非法地址。

  于是在对这些失效的迭代器进行操作的时候 可能导致程序出bug ,或者直接崩溃。

4. 那么该如何删除vector元素呢?

  可以参考:

void del_elem(vector<string> &vec, const char *elem)
{
    vector<string>::iterator itor = vec.begin();
    for (; itor != vec.end();)
    {
        std::cout << *itor << " " << &(*itor) << std::endl;
        if (*itor == elem)
        {
            itor = vec.erase(itor);                 //个人觉得这句赋值是多余的,因为erase本身没有对itor进行任何操作,erase操作之前和操作之后的itor所指向的位置是不变的,变的只是里边的值如有理解错误,还望及时指出
        }
        else
        {
            itor++;
        }
    }
}

5. 想必读者已经对vector删除元素引起的迭代器失效有了一些理解,那么再来理解插入元素导致的迭代器失效会更容易一些。

  1 如果插入操作引起了空间重新配置,(申请新空间,赋旧空间的值到新空间,释放旧空间),那么在插入操作执行后,之前声明的所有迭代器都将失效

  2 如果没有引起空间配置,那么会导致插入位置之后的迭代器失效。

6. 我们 假如我们声明了一些迭代器,对vector进行了插入或删除操作以后,要注意这些迭代器可能已经失效。

7. 由vector的迭代器失效,可以引出,其他序列式容器的迭代器失效,其他关联式容器的迭代器失效。内容太多,本篇只是先给出vector的迭代器失效的一些理解,后续继续补充其它的。

   <effective stl> 第九条,较详细的讨论的各种容器的操作方法,有兴趣的读者可自行翻阅。

水平有限,错误难免,望及时指出。希望能对大家理解迭代器失效提供一些思路。

 

  

  

  

时间: 2024-10-27 18:32:52

c++之迭代器失效的相关文章

STL源码分析--迭代器总结、迭代器失效总结

Vector 1.内部数据结构:连续存储,例如数组. 2.随机访问每个元素,所需要的时间为常量. 3.在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化. 4.可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存. 5.迭代器失效 插入:vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同).当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有

STL迭代器失效总结

转自: http://blog.csdn.net/hackbuteer1/article/details/7734382             http://m.blog.csdn.net/blog/xhu_eternalcc/38355619 迭代器(iterator)是一个可以对其执行类似指针的操作(如:解除引用(operator*())和递增(operator++()))的对象,我们可以将它理 解成为一个指针.但它又不是我们所谓普通的指针,我们可以称之为广义指针,你可以通过sizeof(

stl 迭代器失效

1.对于基于连续内存的容器,容器元素的增删,有可能会导致迭代器的失效.考虑: int main(int argc, char* argv[]) { vector<int> intVec; intVec.push_back(1); intVec.push_back(2); intVec.push_back(3); vector<int>::iterator iter1 = intVec.begin(); vector<int>::iterator iter2 = iter

C++: STL迭代器及迭代器失效问题

转载至:http://blog.csdn.net/wangshihui512/article/details/9791517 迭代器失效: 典型的迭代器失效. 首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效.那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了10个元素,现在又要添加一个元素到容器中,但是内存中紧跟在这10个元素后面没有一个空闲空间,而vector的元素必须顺序存储一边索引访问,所以我们不能在内存中随便找个地方存储

map和vector的迭代器失效问题

当删除一个STL容器(比如map, vector)中的某个元素时, 会引起迭代器失效, 所以, 我们务必提高警惕. 题目: 删除map<int, int>中value为5的倍数的元素. 该题看起来很自然很简单, 实则有迭代器失效的陷阱. 如果对迭代器失效问题一无所知, 则很容易写出如下的错误代码: 1 #include <iostream> 2 #include <map> 3 using namespace std; 4 5 typedef map<int, i

C++中防止STL中迭代器失效——map/set等关联容器——vector/list/deque等序列容器—如何防止迭代器失效—即erase()的使用

序列性容器::(vector和list和deque) erase迭代器不仅使所有指向被删元素的迭代器失效,而且使被 删元素之后的所有迭代器失效,所以不能使用erase(iter++)的方 式,但是erase的返回值为下一个有效的迭代器,所以   正确方法为:: for( iter = c.begin(); iter != c.end(); ) iter = c.erase(iter); 关联性容器::(map和set比较常用) erase迭代器只是被删元素的迭代器失效,但是返回值为void, 所

vector和map的迭代器失效问题

1.vector #include <iostream> #include <string> #include <vector> using namespace std; void vectorTest() { vector<int> container; for (int i = 0; i < 10; i++) { container.push_back(i); } vector<int>::iterator iter; for (ite

stl迭代器失效

迭代器(iterator)是一个可以对其执行类似指针的操作(如:解除引用(operator*())和递增(operator++()))的对象,我们可以将它理解成为一个指针.但它又不是我们所谓普通的指针,我们可以称之为广义指针,你可以通过sizeof(vector::iterator)来查看,所占内存并不是4个字节.     首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效.那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了10个元

容器操作使迭代器失效

一.向容器添加元素时 ①vector & string if 储存空间重新分配 迭代器.引用&指针都失效 if 没有重新分配  插入位置之前都有效 ②deque 插入到除首尾外都会失效 if 首尾添加,迭代器失效,其他不失效 ③list & forward_list 都有效 二.从容器中删除元素 ①list & forward_list 指向容器其他的任有效 ②deque 删除首&非尾的其他,任有效 删除尾元素,都失效 ③vector & string 被删