STL之VectorMapList针对erase方法踩坑笔记

前沿

        如下总结的三种容器,开头都会涉及当前容器的特点,再者就本次针对erase方法的使用避坑总结。

一.Vector

        vector关联关联容器,存储内存是连续,且特点支持快速访问,但是插入和删除效率比较地(需要找查找和移动)。另外在删除元素是,需注意迭代器的失效情况。

erase避坑,示例代码:

int main(){

    //vector
    std::vector<std::string> v;
    v.push_back("one");
    v.push_back("two");
    v.push_back("three");
    v.push_back("three");
    v.push_back("three");
    v.push_back("three");
    v.push_back("four");
    v.push_back("five");

    std::cout<< "del before size - " << v.size() << std::endl;

    for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
    {
        std::cout << *it << std::endl;
    }
    std::cout << "------------------" << std::endl;
    for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
    {
        if(*it == "three")
        {
            v.erase(it);
        }
    }
    std::cout<< "del after size - " << v.size() << std::endl;
    for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
    {
        std::cout << *it << std::endl;
    }
    return 0;
}

输出结果:

[root@bogon fuxi_csdn]# ./a.out 
del before size - 8
one
two
three
three
three
three
four
five
------------------
del after size - 6
one
two
three
three
four
five
[root@bogon fuxi_csdn]#

上述删除前&删除后结果发现未能实现删除全部的three元素,这是何原因呢?请看下文,经查询发现vecotr容器的erase方法实现有关。当vector容器适用erase方法删除元素时,上述代码中通过v.erase(it),传入的是一个迭代器元素,通过查阅官方网站查看erase的定义,详情如下:

c++98:

 上述这段话含义是,从容器中移除单个元素或者从迭代范围内移除元素,并且通过移除元素的操作,有效的减少了容器的大小。因为vector容器底层适用数组作为底层存储结构,所以在移除末尾以外的其他元素,容器内的元素位置会进行元素移动且重新分配位置,这是一个很低效的操作方法。

通过上述文档我们可以知道,vector容器在删除某个元素时(末尾除外),剩余的元素会进行移动,后面的元素会将前面剔除的元素的位置覆盖。如此以来上述输出代码的问题就得意显现出来。那么到底是怎么移动导致的上述问题的呢?看如下图解:

通过上图所示,当找到一个three之后,erase函数内部将当前位置元素剔除掉,在将剩余的元素向前移动。此时it的位置没有发生改变仍旧在原来的位置上,网上说返回了删除元素下一个元素迭代器,概念等价如此,但是实际上原因是因为后面的元素移动了,所以先前删除元素的it此时就指向了移动后的元素,也算是下一个元素的迭代器。在erase的源码:

#define _GLIBCXX_MOVE3(_Tp, _Up, _Vp) std::move(_Tp, _Up, _Vp)
template<typename _Tp, typename _Alloc>
    typename vector<_Tp, _Alloc>::iterator
    vector<_Tp, _Alloc>::
    erase(iterator __position)
    {
      if (__position + 1 != end())
	_GLIBCXX_MOVE3(__position + 1, end(), __position);
      --this->_M_impl._M_finish;
      _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
      return __position;
    }

上述代码,将__position+ 1之后到结尾元素进行移动,在进行处理,最后return位置,仍旧是原来的it位置,指向的就是删除后下一个元素。通过以上图解+代码中的循环(tmp = it ++ => it++),就能解释输出内容为什么会如此了!不仅如此,如上代码,元素为偶数,进行删除操作,不会报错,但是无法达成删除所有元素的处理。巧合迭代器不会越界。当为奇数个数,程序就会崩溃,出现段错误,当迭代去指向最后一个元素时,被删除时,在进行++操作,越界,导致程序崩溃,可以将上述代码删除three修改成删除five即可验证。

例如修改上述代码如下&输出结果:

  for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
    {
        if(*it == "five")
        {
            v.erase(it);
        }
    }

输出:
[root@bogon fuxi_csdn]# ./a.out 
del before size - 8
one
two
three
three
three
three
four
five
------------------
Segmentation fault (core dumped)

删除最有一个元素,因删除后当前的it失效,在进行++it就会越界,从而导致程序崩溃。

知晓了上述代码产生的原因,所以针对上述代码优化修改:

 for(std::vector<std::string>::iterator it = v.begin(); it != v.end(); )
 {
        if(*it == "three")
        {
            it = v.erase(it);
        }else{
            ++it;
        }
 }

另外也可以通过std::remove配合批量删除重复的元素:

 v.erase(std::remove(v.begin(), v.end(), "three"), v.end());// 剔除范围内所有three

上述方式中remove方法先将需要非目标元素全部移动到前面,剩余的局势要删除的元素,最后返回一个迭代器,再通过erase范围性质删除目标元素。源代码如下:std::remove:

 template<typename _ForwardIterator, typename _Tp>
    _ForwardIterator
    remove(_ForwardIterator __first, _ForwardIterator __last,
	   const _Tp& __value)
    {
      // concept requirements
      __glibcxx_function_requires(_Mutable_ForwardIteratorConcept<
				  _ForwardIterator>)
      __glibcxx_function_requires(_EqualOpConcept<
	    typename iterator_traits<_ForwardIterator>::value_type, _Tp>)
      __glibcxx_requires_valid_range(__first, __last);

    // 找到目标元素的第一个位置
      __first = _GLIBCXX_STD_A::find(__first, __last, __value); 
      if(__first == __last)
        return __first;
      _ForwardIterator __result = __first;
      ++__first;
      for(; __first != __last; ++__first)
        if(!(*__first == __value))
          {
            *__result = _GLIBCXX_MOVE(*__first); // 将非目标元素前移动
            ++__result;
          }
      return __result;
    }

代码中先找找到范围内的目标元素的第一个位置,然后利用__result位置为非目标元素的移动存储位置,当元素查找完之后,返回最终的__result位置,那么erase(__result,v.end()),就清理的是所有要删除的目标元素。

二.Map

        Map是一种哈希表结构形式的容器,其底层采用红黑树作为存储结构具有高效的增删查,另外还具备自动排序(属于自定义类型可以指定排序方法,可查看本博的C++之map踩坑记录博文)。实际使用中非常便利,为应用层开发提供高效的开发便利。本次主要讨论的是map容器适用erase时所避的坑,避免实际使用时出过错导致一些列问题。

erase避坑,示例代码:

#include <iostream>
#include <map>
#include <vector>
 
 
int main()
{
    std::map<int, std::string> m;
    m.insert(std::make_pair(1, "one"));
    m.insert(std::make_pair(2, "two"));
    m.insert(std::make_pair(3, "three"));
    m.insert(std::make_pair(4, "four"));
    m.insert(std::make_pair(5, "five"));
 
    std::cout<< "before erase" << std::endl;
    for (std::map<int, std::string>::iterator it = m.begin(); it != m.end(); ++it)
    {
        std::cout << it->first << " " << it->second << std::endl;
    }
 
    for(std::map<int, std::string>::iterator it = m.begin(); it != m.end();)
    {
        if(it->first == 3)
        {
            //m.erase(it); //该处会崩溃
            m.erase(it++); // 正确用法
        }else
        {
            ++it;
        }
    }
 
    std::cout << "After erase" << std::endl;
    for (std::map<int, std::string>::iterator it = m.begin(); it != m.end(); ++it)
    {
        std::cout << it->first << " " << it->second << std::endl;
    }
    return 0;
}

崩溃输出:

before erase
1 one
2 two
3 three
4 four
5 five
ret it = 3
Segmentation fault (core dumped)

正常输出:

root@ubu-virtual-machine:~# ./a.out 
before erase
1 one
2 two
3 three
4 four
5 five
ret it = 4
After erase
1 one
2 two
4 four
5 five

 上述代码在c++98跟c++11对应的删除有偏差:

98版本的erase都是返回的整形,如果按照上代码实现,出现崩溃问题,后经过查询发现stl内部erase的实现,当调用时,会拷贝一份当前迭代器,之后如果没将it移动,那么当前的it就会失效,从而导致程序崩溃异常。正确的用法通过v.erase(it++)联合++it。在erase(it++)调用内部实现流程时,erase临时拷贝一份当前迭代器,因it++作为参数,其优先级比函数调用优先级高,所以erase流程为先拷贝,在走it++,此时迭代器已经就走到删除元素的下一个位置,如此一来,即可正常遍历运行。

上述途中c++11中优化了erase方法,剔除元素后返回删除元素的下一个元素的迭代器。使用时需要注意方式:

for(std::map<int, std::string>::iterator it = m.begin(); it != m.end();)
    {
        if(it->second == "three")
        {
            it = m.erase(it);// or m.erase(it++);
            std::cout<<"ret it = "<<it->first<<std::endl;
        }else{
            ++it;
        }
    }

 针对上述c++11看下优化后的erase源码:

  _GLIBCXX_ABI_TAG_CXX11
      iterator
      erase(iterator __position)
      { return _M_t.erase(__position); }

    _GLIBCXX_ABI_TAG_CXX11
      iterator
      erase(const_iterator __position)
      {
	const_iterator __result = __position; // 拷贝
	++__result;// 指向下个元素
	_M_erase_aux(__position); // 销毁要删除的元素
	return __result._M_const_cast();// 返回下个元素
      }

上述的erase方法实现,先拷贝,定义下个元素迭代器,销毁目标元素,返回删除的下个元素。

三.List

        List是一个双向链表容器,它有一些特定的优点和缺点,适用于不同的场景。其优点高效的插入和删除操作,双向链表,支持双向遍历,内存碎片化较小,不需要频繁的内存重新分配。但是也存在一些缺点,较高的内存开销,如额外的指针内存。不支持随机访问,关联容器,因其链结构,迭代器每次都要指针跳转,性能不如直接访问快。

erase避坑,示例代码:

int main()
{
    std::list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    std::cout<< "before erase" << std::endl;
    for(std::list<int>::iterator it = l.begin(); it != l.end(); ++it)
    {
        std::cout << *it << std::endl;
    }

    for(std::list<int>::iterator it = l.begin(); it != l.end();++it)
    {
        if(*it == 2)
        {
            l.erase(it); // 会崩溃
        }
    }

    std::cout<< "after erase" << std::endl;
    for(std::list<int>::iterator it = l.begin(); it != l.end(); ++it)
    {
        std::cout << *it << std::endl;
    }
    return 0;
}

输出:

[root@bogon fuxi_csdn]# ./a.out 
before erase
1
2
3
Segmentation fault (core dumped)

如上出现段错误。何故? 查看list内部实现的erase,跟前面vector跟map的erase相似,都是剔除当前元素后返回下一个元素的迭代器,源码如下:

 template<typename _Tp, typename _Alloc>
    typename list<_Tp, _Alloc>::iterator
    list<_Tp, _Alloc>::
    erase(iterator __position)
    {
      iterator __ret = iterator(__position._M_node->_M_next);
      _M_erase(__position);
      return __ret;
    }

 代码中先进行next操作,然后销毁当前要删除元素,return返回__ret表示下个元素位置。所以循环中使用erase需要注意方式同map方式一样即可,跟改为:

 for(std::list<int>::iterator it = l.begin(); it != l.end();)
 {
    if(*it == 2)
    {
        l.erase(it++);
    }else   {
        ++it;
    }
 }

总结,stl库提拱了方便的存储结构供给我们日常使用,在使用时需要注意潜在的风险问题,避免实际应用时出现不可预期的问题,以上就是vector & map & list 容器的erase方法在循环中使用需要注意的坑点,当然还有其他容器适用删除方法结合实际情况注意!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/953359.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Rust】引用与借用

目录 思维导图 1. 引用与借用的基本概念 1.1. 引用示例 2. 借用的规则 2.1. 可变借用示例 2.2. 借用的限制 3. 引用的生命周期 思维导图 1. 引用与借用的基本概念 引用的定义&#xff1a;引用是一种指向数据的指针&#xff0c;但与裸指针不同&#xff0c;Rust的引用在编…

《自动驾驶与机器人中的SLAM技术》ch8:基于 IESKF 的紧耦合 LIO 系统

紧耦合系统&#xff0c;就是把点云的残差方程直接作为观测方程&#xff0c;写入观测模型中。这种做法相当于在滤波器或者优化算法内置了一个 ICP 或 NDT。因为 ICP 和 NDT 需要迭代来更新它们的最近邻&#xff0c;所以相应的滤波器也应该使用可以迭代的版本&#xff0c;ESKF 对…

Mac 删除ABC 输入法

参考链接&#xff1a;百度安全验证 Mac下删除系统自带输入法ABC&#xff0c;正解&#xff01;_mac删除abc输入法-CSDN博客 ABC 输入法和搜狗输入法等 英文有冲突~~ 切换后还会在英文状态&#xff0c;可以删除 &#xff1b;可能会对DNS 输入有影响&#xff0c;但是可以通过复…

1.13 多线程编程

1.思维导图 2.创建两个子进程&#xff0c;父进程负责&#xff1a;向文件中写入数据&#xff1b;两个子进程负责&#xff1a;从文件中读取数据。 要求&#xff1a;一定保证1号子进程先读取&#xff0c;2号子进程后读取&#xff0c;使用文件IO去实现。 1>程序代码 …

Elasticsearch ES|QL 地理空间索引加入纽约犯罪地图

可以根据地理空间数据连接两个索引。在本教程中&#xff0c;我将向你展示如何通过混合邻里多边形和 GPS 犯罪事件坐标来创建纽约市的犯罪地图。 安装 如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话&#xff0c;请参考如下的链接来进行安装。 如何在 Linux&#xff0…

数据分析-使用Excel透视图/表分析禅道数据

背景 禅道&#xff0c;是目前国内用得比较多的研发项目管理系统&#xff0c;我们常常会用它进行需求管理&#xff0c;缺陷跟踪&#xff0c;甚至软件全流程的管理&#xff0c;如果能将平台上的数据结公司的实际情况进行合理的分析利用&#xff0c;相信会给我们的项目复盘总结带来…

【c语言】指针 (完结)

一、sizeof和strlen的对比 1、sizeof 前面我们在学习操作符的时候&#xff0c;我们学习了sizeof&#xff0c;知道其是计算变量所占内存的大小的&#xff0c;单 位是字节&#xff0c;如果操作数是数据类型的话&#xff0c;计算的就是这个类型的变量所占的内存空间的大…

Chromium 132 编译指南 Windows 篇 - 生成构建文件 (六)

1. 引言 在上一篇文章中&#xff0c;我们已经成功获取了 Chromium 的源代码并同步了相关的第三方依赖。本文将继续深入&#xff0c;指导您如何使用 GN 工具生成构建文件&#xff0c;为接下来的编译工作奠定基础。 2. 切换 Chromium 版本至 132 在开始正式构建之前&#xff0…

(12)springMVC文件的上传

SpringMVC文件上传 首先是快速搭建一个springMVC项目 新建项目mvn依赖导入添加webMoudle添加Tomcat运行环境.在配置tomcat时ApplicationContext置为"/"配置Artfact的lib配置WEB-INF配置文件&#xff08;记得添加乱码过滤&#xff09;配置springmvc-servlet文件&…

3D目标检测数据集——Waymo数据集

Waymo数据集簡介 发布首页&#xff1a;https://waymo.com/open/ 论文&#xff1a;https://openaccess.thecvf.com/content_CVPR_2020/papers/Sun_Scalability_in_Perception_for_Autonomous_Driving_Waymo_Open_Dataset_CVPR_2020_paper.pdf github&#xff1a;https://github.…

Mysql--运维篇--空间管理(表空间,索引空间,临时表空间,二进制日志,数据归档等)

MySQL的空间管理是指对数据库存储资源的管理和优化。确保数据库能够高效地使用磁盘空间、内存和其他系统资源。良好的空间管理不仅有助于提高数据库的性能&#xff0c;还能减少存储成本并防止因磁盘空间不足导致的服务中断。MySQL的空间管理涉及多个方面&#xff0c;包括表空间…

STM32之LWIP网络通讯设计-下(十五)

STM32F407 系列文章 - ETH-LWIP&#xff08;十五&#xff09; 目录 前言 一、软件设计 二、CubeMX实现 1.配置前准备 2.CubeMX配置 1.ETH模块配置 2.时钟模块配置 3.中断模块配置 4.RCC及SYS配置 5.LWIP模块配置 3.生成代码 1.main文件 2.用户层源文件 3.用户层头…

Gateway 网关

1.Spring Cloud Gateway Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关&#xff0c;Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式&#xff0c;Spring Cloud Gateway作为Spring Cloud生态…

数据结构:栈(Stack)和队列(Queue)—面试题(二)

1. 用队列实现栈。 习题链接https://leetcode.cn/problems/implement-stack-using-queues/description/描述&#xff1a; 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&a…

在 .NET 9 中使用 Scalar 替代 Swagger

前言 在.NET 9发布以后ASP.NET Core官方团队发布公告已经将Swashbuckle.AspNetCore&#xff08;一个为ASP.NET Core API提供Swagger工具的项目&#xff09;从ASP.NET Core Web API模板中移除&#xff0c;这意味着以后我们创建Web API项目的时候不会再自动生成Swagger API文档了…

双模充电桩发展前景:解锁新能源汽车未来的金钥匙,市场潜力无限

随着全球能源转型的浪潮席卷而来&#xff0c;新能源汽车行业正以前所未有的速度蓬勃发展&#xff0c;而作为其坚实后盾的充电基础设施&#xff0c;特别是双模充电桩&#xff0c;正逐渐成为推动这一变革的关键力量。本文将从多维度深入剖析双模充电桩的市场现状、显著优势、驱动…

Notepad++上NppFTP插件的安装和使用教程

一、NppFTP插件下载 图示是已经安装好了插件。 在搜索框里面搜NppFTP&#xff0c;一般情况下&#xff0c;自带的下载地址容易下载失败。这里准备了一个下载连接&#xff1a;Release v0.29.10 ashkulz/NppFTP GitHub 这里我下载的是x86版本 下载好后在nodepad的插件里面选择打…

Mysql--运维篇--备份和恢复(逻辑备份,mysqldump,物理备份,热备份,温备份,冷备份,二进制文件备份和恢复等)

MySQL 提供了多种备份方式&#xff0c;每种方式适用于不同的场景和需求。根据备份的粒度、速度、恢复时间和对数据库的影响&#xff0c;可以选择合适的备份策略。主要备份方式有三大类&#xff1a;逻辑备份&#xff08;mysqldump&#xff09;&#xff0c;物理备份和二进制文件备…

在 Safari 浏览器中,快速将页面恢复到 100% 缩放(也就是默认尺寸)Command (⌘) + 0 (零)

在 Safari 浏览器中&#xff0c;没有一个专门的快捷键可以将页面恢复到默认的缩放比例。 但是&#xff0c;你可以使用以下两种方法快速将页面恢复到 100% 缩放&#xff08;也就是默认尺寸&#xff09;&#xff1a; 方法一&#xff1a;使用快捷键 (最常用) Command (⌘) 0 (零…

LLMs之RAG:《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读

LLMs之RAG&#xff1a;《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读 导读&#xff1a;这篇论文针对在资源受限的边缘设备上部署检索增强生成 (RAG) 系统的挑战&#xff0c;提出了一种名为 EdgeRAG 的高效方法。EdgeRAG 通过巧妙地结合预计算、在线生成和缓存策…