• 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> 第九条,较详细的讨论的各种容器的操作方法,有兴趣的读者可自行翻阅。

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

      

      

      

      

  • 相关阅读:
    css3圆形轨迹动画
    css3动画
    3D效果
    css3基础下
    css3基础
    HTML5 表单 中
    HTML5 表单
    面向对象的三大特性、七大原则、类与类间的关系
    四种事务的隔离级别
    线程池(二)
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/6883122.html
Copyright © 2020-2023  润新知