当前位置: 首页 > news >正文

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

前沿

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

一.Vector

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

erase避坑,示例代码:

int main(){//vectorstd::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>::iteratorvector<_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>_ForwardIteratorremove(_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_CXX11iteratorerase(iterator __position){ return _M_t.erase(__position); }_GLIBCXX_ABI_TAG_CXX11iteratorerase(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>::iteratorlist<_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方法在循环中使用需要注意的坑点,当然还有其他容器适用删除方法结合实际情况注意!!!

相关文章:

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

前沿 如下总结的三种容器&#xff0c;开头都会涉及当前容器的特点&#xff0c;再者就本次针对erase方法的使用避坑总结。 一.Vector vector关联关联容器&#xff0c;存储内存是连续&#xff0c;且特点支持快速访问&#xff0c;但是插入和删除效率比较地(需要找查找和移动)。另…...

梯度下降法为什么要提前停止

什么是提前停止&#xff08;Early Stopping&#xff09;&#xff1f; 提前停止是一种正则化技术&#xff0c;用于在训练机器学习模型&#xff08;特别是神经网络&#xff09;时防止过拟合。它的核心思想是通过监控模型在验证集上的性能&#xff0c;在性能开始恶化之前停止训练…...

【vue3项目使用 animate动画效果】

vue3项目使用 animate动画效果 前言一、下载或安装npm 安装 二、引入组件三、复制使用四、完整使用演示总结 前言 提示&#xff1a;干货篇&#xff0c;不废话&#xff0c;点赞收藏&#xff0c;用到会后好找藕~ 点击这里&#xff0c;直接看官网哦 &#x1f449; 官网地址&#…...

1.1.1 C语言常用的一些函数(持续更新)

总框架见&#xff08;0. 总框架-CSDN博客&#xff09; &#xff08;1&#xff09;socket (a)分配fd&#xff1b;(b)分配tcp控制块(tcb) int socket(int domain, int type, int protocol);AF_INET IPv4 Internet protocols ip(7)AF_INET6 IP…...

李宏毅机器学习课程笔记03 | 类神经网络优化技巧

文章目录 类神经网络优化技巧局部最小值local minima 与 鞍点saddle pointSaddle Point 的情况更常见 Tips for training&#xff1a;Batch and MomentumSmall Batch vs Large Batch回顾&#xff1a;optimization优化 找到参数使L最小问题&#xff1a;为什么要用Batch&#xff…...

简洁明快git入门及github实践教程

简洁明快git入门及github快速入门实践教程 前言git知识概要&#xff1a;一&#xff1a;什么是 Git&#xff1f;二&#xff1a;安装 Git三&#xff1a;配置 Git配置git的用户名和邮箱地址创建仓库 四&#xff1a;Git实践五&#xff1a;远程仓库操作&#xff08;基于git命令使用G…...

Python使用socket实现简易的http服务

在接触的一些项目中&#xff0c;有时为了方便可视化一些服务状态&#xff08;请求数很少&#xff09;&#xff0c;那么很容易想到使用http服务来实现。但开源的web后端框架&#xff0c;例如flask&#xff0c;fastapi&#xff0c;django等略显沉重&#xff0c;且使用这些框架会有…...

【Hive】海量数据存储利器之Hive库原理初探

文章目录 一、背景二、数据仓库2.1 数据仓库概念2.2 数据仓库分层架构2.2.1 数仓分层思想和标准2.2.2 阿里巴巴数仓3层架构2.2.3 ETL和ELT2.2.4 为什么要分层 2.3 数据仓库特征2.3.1 面向主题性2.3.2 集成性2.3.3 非易失性2.3.4 时变性 三、hive库3.1 hive概述3.2 hive架构3.2.…...

linux系统监视(centos 7)

一.系统监视 1.安装iostat&#xff0c;sar&#xff0c;sysstat&#xff08;默认没有&#xff0c;安装过可以跳跃&#xff09; iostat 和 sar&#xff1a; 同样&#xff0c;iostat 和 sar 是 sysstat 软件包的一部分。使用以下命令安装&#xff1a;sudo yum install sysstat解释…...

Blazor中Syncfusion图像编辑器组件使用方法

Blazor中Syncfusion图像编辑器组件是一个功能丰富的图像处理工具&#xff0c;支持多种编辑、操作和交互方式&#xff0c;帮助用户高效处理图像。以下是该组件的主要功能总结&#xff1a; 主要功能&#xff1a; 图像打开与保存 图像编辑器允许用户通过简单的点击操作打开支持的…...

电动汽车V2G技术Matlab/Simulink仿真模型

今天给大家更新关于V2G技术的仿真&#xff0c;不是研究这个方向的&#xff0c;可能会对这个名称比较陌生&#xff0c;那么&#xff0c;什么是“V2G”&#xff1f; V2G全称&#xff1a;Vehicle-to-Grid&#xff0c;即车网互动&#xff0c;利用电动汽车特有的储能功能与电网“双…...

C++中的unordered_set和unordered_map的模拟实现

一、封装基本结构 与map和set的封装过程很想&#xff0c;unordered_set和unordered_map也需要用MapKeyOfT和SetKeyOfT创建哈希表类型&#xff0c;借此获取对应的key值来使用&#xff1b; 因此&#xff0c;在哈希表中也一样需要用参数class T来替代set中的key和map中的pair<…...

Spring Boot 2 学习指南与资料分享

Spring Boot 2 学习资料 Spring Boot 2 学习资料 Spring Boot 2 学习资料 在当今竞争激烈的 Java 后端开发领域&#xff0c;Spring Boot 2 凭借其卓越的特性&#xff0c;为开发者们开辟了一条高效、便捷的开发之路。如果你渴望深入学习 Spring Boot 2&#xff0c;以下这份精心…...

(一)QSQLite3库简介

1、SQLite数据库 SQLite数据库&#xff0c;作为一个轻量级的关系型数据库管理系统&#xff0c;广泛应用于移动设备和桌面应用程序中。由于其简单易用、无需配置的特点&#xff0c;它为开发者提供了极大的便利。然而&#xff0c;正是由于其应用广泛&#xff0c;随着用户对于系统…...

《计算机网络》课后探研题书面报告_网际校验和算法

网际校验和算法 摘 要 本文旨在研究和实现网际校验和&#xff08;Internet Checksum&#xff09;算法。通过阅读《RFC 1071》文档理解该算法的工作原理&#xff0c;并使用编程语言实现网际校验和的计算过程。本项目将对不同类型的网络报文&#xff08;包括ICMP、TCP、UDP等&a…...

hot100_240. 搜索二维矩阵 II

hot100_240. 搜索二维矩阵 II 直接遍历列减行增 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,1…...

78_Redis网络模型

1.Redis网络模型概述 1.1 Redis网络模型介绍 Redis 7.x的网络模型基于epoll的Reactor模式实现,这是一个高效的事件驱动模型。在Redis中,所有的网络事件(如连接、读写等)都由一个事件循环(Event Loop)来处理。这个事件循环负责监听套接字上的事件,并根据事件类型调用相…...

python范围

用户图形界面-工资计算器 from tkinter import *def f():w int(e1.get()) int(e2.get()) - int(e3.get())wage.insert(0,w)root Tk() root.title("工资计算器") Label(root, text"每月基本工资&#xff1a;").pack() e1 Entry(root) e1.pack() Label(…...

vulnhub靶场【Raven系列】之2 ,对于mysql udf提权的复习

前言 靶机&#xff1a;Raven-2&#xff0c;IP地址为192.168.10.9 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.2 都采用虚拟机&#xff0c;网卡为桥接模式 文章所用靶机来自vulnhub&#xff0c;可通过官网下载&#xff0c;或者通过链接:https://pan.quark.cn/s/a65…...

基于vite+vue3+mapbox-gl从零搭建一个项目

下面是基于 Vite、Vue 3 和 Mapbox GL 从零搭建一个项目的完整步骤&#xff0c;包括环境搭建、依赖安装、配置和代码示例。 1. 初始化项目 首先&#xff0c;使用 Vite 快速创建一个 Vue 3 项目&#xff1a; npm create vuelatest vue3-mapboxgl --template vue cd vue3-mapbo…...

向harbor中上传镜像(向harbor上传image)

向 Harbor 中上传镜像通常分为以下几个步骤&#xff1a; 1、登录 Harbor 2、构建镜像 3、标记镜像 4、推送镜像到 Harbor 仓库 1、登录 Harbor 首先&#xff0c;确保你已经能够访问 Harbor&#xff0c;并且已经注册了账户。如果还没有 Harbor 账户&#xff0c;你需要先注册一…...

【线性代数】行列式的性质

行列式性质定理讲义 一、行列式的基本性质 性质 1&#xff1a;行列互换 对于任意一个 n n n \times n nn 的方阵 A A A&#xff0c;其行列式 ∣ A ∣ |A| ∣A∣ 满足&#xff1a; ∣ A ∣ ∣ A T ∣ |A| |A^T| ∣A∣∣AT∣ 其中&#xff0c; A T A^T AT 是 A A A 的…...

智能家居企业如何通过设计师渠道打造第二曲线?

随着智能家居行业的迅速发展和消费者需求的不断升级&#xff0c;企业的营销策略也在不断变化。传统的B2C营销模式逐渐让位于更加精细化、定制化的B2B2C模式&#xff0c;其中设计师渠道的开发与合作&#xff0c;成为智能家居企业布局市场、提升品牌影响力的关键。 智能家居推广的…...

Unity3d 实时天气系统基于UniStorm插件和xx天气API实现(含源码)

前言 实时天气在Unity3d三维数字沙盘中的作用非常重要&#xff0c;它能够增强虚拟环境的真实感和互动性&#xff0c;实时天气数据的应用可以提供更为精准和直观的天气信息支持&#xff0c;如果真实的数据加上特效、声音和模型反馈会提高产品档次&#xff0c;提高真实感。 目前…...

年后找工作需要注意的事项

大家好&#xff01;我是 [数擎 AI]&#xff0c;一位热爱探索新技术的前端开发者&#xff0c;在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情&#xff0c;欢迎关注我的文章&#xff0c;我们一起成长、进步&#xff01; 开发领域&#xff1a;前端开发 | A…...

模拟器多开窗口单IP与代理IP关系

模拟器多开窗口同IP背后出现的问题 在游戏世界中&#xff0c;模拟器多开窗口是玩家们提升体验的常见做法。通过在同一设备上开启多个模拟器窗口&#xff0c;玩家可以同时运营多个游戏账号&#xff0c;增加游戏的趣味性和效率。 一旦检测到一个IP地址下登录了过多的账号&#x…...

Android ScrollView嵌套X5WebView大片空白问题

scrollview嵌套后webview的高度不可控。留有大片空白。 注&#xff1a;官方不建议scrollview嵌套webview 最好让webview自身滚动 解决方案&#xff1a; act_news_detail_wv.setWebViewClient(new WebViewClient() {Overridepublic void onPageFinished(WebView webView, Str…...

Java Web开发进阶——WebSocket与实时通信

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议&#xff0c;广泛应用于需要实时数据交换的应用程序中。它能够实现服务器与客户端之间的双向通信&#xff0c;避免了传统 HTTP 请求/响应的延迟。结合 Spring Boot&#xff0c;开发实时通信应用变得更加高效与简便。 1. …...

zerotier搭建虚拟局域网,自建planet

基于该开源项目 自建planet节点&#xff0c;更快速&#xff0c;更安全 本教程依据docker-zerotier-planet 项目文档书写&#xff0c;并以linux(centos 7)和windows作为示例&#xff0c;需要其他系统配置方法&#xff0c;可移步项目文档 一. 前置资源 具有外网ip的服务器 后面…...

SQL面试题1:连续登陆问题

引言 场景介绍&#xff1a; 许多互联网平台为了提高用户的参与度和忠诚度&#xff0c;会推出各种连续登录奖励机制。例如&#xff0c;游戏平台会给连续登录的玩家发放游戏道具、金币等奖励&#xff1b;学习类 APP 会为连续登录学习的用户提供积分&#xff0c;积分可兑换课程或…...