菜鸟笔记
提升您的技术认知

菜鸟教程

rapidjson 构造数组将函数实现放在头文件中RapidJSON解析和生成Json绝对路径和相对路径gcc 优化选项 -O1 -O2 -O3 -Os 优先级Python求一个整数位数的方法python中判断数字位数的几种方法python计算分位数查看hive的版本如何查看hadoop的版本elasticsearch查看所有indexsublime 列模式 列编辑shell遍历目录下所有文件http-parser解析http报文详解supervisor守护工具配置lnmp为已有站点手动更新ssl证书记录java 最长回文、最长回文子串ssh免密码登录为Linux内核开启BBR加速frp服务开机自启x86架构下,页面大小为什么是4K?如何限制对象只能建立在堆上或者栈上进程间通信的方式SyntaxError: Non-ASCII character '\xe5'TCP 连接半关闭问题ucontext 协程库代码分析python中yield的用法详解为PHP7.0安装redis扩展ucontext 简单协程库ImportError: No module named cv2的完美解决方法HashMap扩容全过程关于HashMap常见面试考点(底层原理+扩容机制)DOS批处理中的字符串处理详解(字符串截取)Linux下如何查找.sh后缀的文件core文件出现“is not a core dump: File format not recognized”的原因和解决方法(看/proc/pid/limits)记一次docker问题定位(perf,iostat等性能分析)Protocol Buffers 2.5.0 安装python 使用thrift序列化与反序列化对象PHP的base64_decode乱码linux中后台运行 java -jarLinux常用命令--文件搜索Linux常用命令--文件基础操作与文件权限变更Linux常用命令--软件包管理之(RPM包管理)Linux常用命令--文件编辑与查看Linux常用命令--文件压缩与挂载Linux常用命令--系统管理Linux常用命令--系统管理之(用户管理、用户组管理)Linux常用命令--系统管理之(进程管理、定时任务、系统监控)Linux常用命令--软件包管理之(服务管理)Linux常用命令--软件包管理之(yum与源码包安装)Linux初窥:Linux下SSH免密码登录配置如何查看CentOS7的版本信息Linux环境变量文件介绍Linux下Tomcat的安装与配置CentOS解决-bash: vim: command not found10 个提高效率的 Linux 命令别名shell比较浮点数和整数crontab 30秒执行一次Python中字典合并的四种方法shell查看CPU 硬盘 内存使用率Python 四大主流 Web 编程框架python:web后台框架简单实现如何使用 BRPC Dummy Serverlist、vector使用erase()时需要注意的地方——迭代器失效容器删除元素后迭代器失效_STL源码剖析——vector容器STL的erase()陷阱-迭代器失效总结.bashrc中定义实用的别名和函数

list、vector使用erase()时需要注意的地方——迭代器失效

阅读 : 149

先说一下两者的优缺点吧。

list与vector的区别

vector相当于一个数组。
    在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。STL内部实现时,首先分配一个非常大的内存空间预备进行存储,即capacituy()函数返回的大小,当超过此分配的空间时再整体重新放分配一块内存存储,这给人以vector可以不指定vector即一个连续内存的大小的感觉。通常此默认的内存分配能完成大部分情况下的存储。
   优点:(1) 不指定一块内存大小的数组的连续存储,即可以像数组一样操作,但可以对此数组
                    进行动态操作。通常体现在push_back() pop_back()
               (2) 随机访问方便,即支持[ ]操作符和vector.at()
               (3) 节省空间
   缺点:(1) 在内部进行插入删除操作效率低
               (2) 只能在vector的最后进行push和pop,不能在vector的头进行push和pop。
               (3) 当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放 。

 list(双向链表)
    每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
   优点:(1) 不使用连续内存完成动态操作。
               (2) 在内部方便的进行插入和删除操作
               (3) 可在两端进行push、pop
   缺点:(1) 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
               (2) 相对于verctor占用内存多

 

问题情景

分别使用vector、list容器实现对1, 4, 3, 7, 9, 3, 6, 8, 3, 3, 5, 2, 3, 7的存入,并且删除其中所有的3,最后输出显示剩下的数字。

面对这个问题,喵哥首先想到的是用erase删除,那么在list和vector中使用erase一样么?

vector使用erase

vector使用erase删除元素,其返回值指向下一个元素,但是由于vector本身的性质(存在一块连续的内存上),删掉一个元素后,其后的元素都会向前移动,所以此时指向下一个元素的迭代器其实跟刚刚被删除元素的迭代器是一样的。

图中的1001、1002~……表示内存地址的关系。

一下是喵哥的解决方案:

#include <vector>
#include <iostream>
using namespace std;

int main()
{
    int a[] = {1, 4, 3, 7, 9, 3, 6, 8, 3, 3, 5, 2, 3, 7};
    vector<int> vector_int(a, a + sizeof(a)/sizeof(int));




/*方案一*/
    // for(int i = 0; i < vector_int.size(); i++)
    // {
    //     if(vector_int[i] == 3)
    //     {
    //         vector_int.erase(vector_int.begin() + i);
    //         i--;
    //     }
    // } 

/*方案二*/
    // for(vector<int>::iterator itor = vector_int.begin(); itor != vector_int.end(); ++itor)
    // {
    //     if (*itor == 3)
    //     {
    //         vector_int.erase(itor);
    //         --itor;
    //     }

    // }

/*方案三*/
vector<int>::iterator v = vector_int.begin();
while(v != vector_int.end())
{
    if(*v == 3)
    {
        v = vector_int.erase(v);
        cout << *v << endl;
    }
    else{
        v++;
    }
}

/*方案四*/
// vector<int>::iterator v = vector_int.begin();
// while(v != vector_int.end())
// {
//     if(*v == 3)
//     {
//         vector_int.erase(v); 
//     }
//     else{
//         v++;
//     }
// }

    for(vector<int>::iterator itor = vector_int.begin(); itor != vector_int.end(); itor++)
    {
        cout << * itor << "  ";
    }
    cout << endl;
    return 0;
}

一共有四种方案。

方案一表明vector可以用下标访问元素,显示出其随机访问的强大。并且由于vector的连续性,且for循环中有迭代器的自加,所以在删除一个元素后,迭代器需要减1。

方案二与方案一在迭代器的处理上是类似的,不过对元素的访问采用了迭代器的方法。

方案三与方案四基本一致,只是方案三利用了erase()函数的返回值是指向下一个元素的性质,又由于vector的性质(连续的内存块),所以方案四在erase后并不需要对迭代器做加法。

 

list使用erase

list的空间不是连续的,所以删除一个元素后,其余的元素不会发生变化,那么在删除一个元素后就需要对迭代器做加法使其指向下一个元素,或者利用erase的返回值(指向下一个元素的迭代器)。

图中的1001、2002……表示内存地址的关系。

#include <list>
#include <iostream>
using namespace std;

int main()
{
   int a[] = {1, 4, 3, 7, 9, 3, 6, 8, 3, 3, 5, 2, 3, 7};
    list<int> list_int(a, a + sizeof(a)/sizeof(int));


///
/*
方法一

*/
    // for(list<int>::iterator itor = list_int.begin(); itor != list_int.end(); ++itor)
    // {
    //     if (*itor == 3)
    //     {
    //         list_int.erase(itor);
    //         itor--;
    //     }

    // }

/*
方法二
*/
// list<int>:: iterator l = list_int.begin();
// while(l != list_int.end())
// {
//     if(*l == 3)
//      {
//          l = list_int.erase(l);
//     }
//     else
//     {
//         l++;
//     }
// }

/*
方法三
*/

list<int>:: iterator l = list_int.begin();
while(l != list_int.end())
{
    if(*l == 3)
    {
        list_int.erase(l++);
        // cout << *l << endl;
    }
    else
    {
        l++;
    }
}



    for(list<int>::iterator itor = list_int.begin(); itor != list_int.end(); itor++)
    {
        cout << * itor << "  ";
    }
    cout << endl;
    return 0;
}

一共三个方案,其中的方案二跟方案三大致一样,只是方案二利用了erase的返回值是指向下一个元素的迭代器的性质。

值得注意的是,方案一中在删除一个元素后还对迭代器做了减法,原因是list执行erase后,被删除元素的迭代器已经失效,无法再做自加,程序会崩溃。所以先做自减(暂时不知道为什么只能做自减,不能做自加,猜测list在使用erase后没有删掉前驱指针?)。

另外,方案三的list_int.erase(l++);不可以分解为list_int.erase(l);和l++;,这样程序会崩溃,因为l++其返回值是l,并且是完成l自加后才执行erase的。

最后推荐一个在Ubuntu下超好用的绘图软件Pinta,功能强大,有图层编辑。

 

//2018年11月28日10:51:03

回答一下上面自己的疑问。

list在执行删除后,其余的迭代器有效,但是被删除的迭代器失效,不可以再对其做自加自减的,一上代码我是在Ubuntu的gcc4.8.4编译的,但是在Windows的VS2015中运行,程序就会崩溃。

同理,在vector中也不可以这样使用(方法二)。

可以考虑把itor的自减放到erase里面,即

.erase(itor--);

这样就在执行删除迭代器前,itor就自减了,不会发生崩溃。并且后置的自减可以做到返回值还是自减前的取值。