迭代器失效:99%的C++程序员都会踩的坑 !
大家好,我是小康。
你踩过这个坑吗?为什么我的程序明明很简单,却总是莫名其妙地崩溃!
嘿,各位 C++ 爱好者们,今天咱们聊一个几乎所有 C++ 程序员都会踩的坑——迭代器失效。无论你是刚入门的新手,还是写了好几年代码的老司机,这个问题都可能让你的程序莫名其妙地崩溃。不过别担心,读完这篇文章,你一定会恍然大悟:"哦!原来是这么回事!"
微信搜索 「跟着小康学编程」,关注我,后续还有更多硬核技术文章分享,带你玩转 Linux C/C++ 编程!
迭代器到底是个啥?
先别急着谈"失效",咱们得先弄明白迭代器是啥玩意儿。
想象一下,迭代器就像是一个"指针",指向容器(比如vector、list)中的某个元素。通过迭代器,我们可以访问、修改容器中的元素,还能在容器中移动(前进或后退)。
简单来说,迭代器就是容器和算法之间的桥梁,让你能够不关心容器内部结构,就能轻松遍历和操作容器中的元素。
vector<int> nums = {1, 2, 3, 4, 5};
// it就是一个迭代器,指向vector的第一个元素
auto it = nums.begin();
cout << *it << endl; // 输出1
什么是迭代器失效?
现在到了关键问题:什么是迭代器失效?
简单讲,当你对容器进行了某些操作后,原先有效的迭代器变得无效了,再使用这个迭代器就会导致未定义行为(通常是程序崩溃),这就是迭代器失效。
就好比你拿着一把钥匙(迭代器)去开一个门(访问容器元素),但有人趁你不注意把锁换了(容器结构改变),你的钥匙自然就不管用了。
常见的迭代器失效场景
1. vector中的迭代器失效
vector 是最常用的 STL 容器,也是迭代器失效最容易发生的地方。
场景一:添加元素(push_back)导致的失效
vector<int> nums = {1, 2, 3};
auto it = nums.begin(); // it指向1
nums.push_back(4); // 可能导致迭代器失效
cout << *it << endl; // 危险操作!可能崩溃
为啥会失效?因为 vector 在内存中是连续存储的,当空间不够时,会重新分配一块更大的内存,并把原来的元素复制过去。这时候,原来的内存地址就变了,之前的迭代器自然就失效了。
就像你正在看一本书,突然有人把这本书拿走换了一本新的放在原处——你手指的位置自然就不对了。
场景二:insert 操作导致的失效
说到 vector 添加元素,咱们可不能忘了另一个常用的操作——insert!这家伙比 push_back 还要狡猾呢!
vector<int> nums = {1, 2, 3, 4, 5};
auto it = nums.begin() + 2; // it指向元素3
nums.insert(nums.begin(), 0); // 在最前面插入0
cout << *it << endl; // 危险操作!it已经失效了
为啥 insert 更容易让人踩坑?因为 insert 有双重杀伤力:
首先,和 push_back 一样,如果 vector 容量不够,insert会导致重新分配内存,所有迭代器就全军覆没了。
其次,即使没有重新分配内存,insert也会让插入位置及其后面的所有元素向后挪位置,这会使这些位置的迭代器全部"串位"。
打个比方,就像你排队时,突然有人插队到你前面,你和你后面的人都被迫向后移了一位——原来记录的位置信息就全乱套了!
记住这个简单规则:
- 如果 insert 导致扩容:所有迭代器都 GG
- 如果 insert 不导致扩容:插入位置及其后面的迭代器都 GG
场景三:删除元素导致的失效
vector<int> nums = {1, 2, 3, 4, 5};
for (auto it = nums.begin(); it != nums.end(); ++it) {
if (*it == 3) {
nums.erase(it); // 迭代器失效
cout << *it << endl; // 不要继续使用it,危险操作!可能崩溃
}
}
问题在哪?当你删除了一个元素后,该位置后面的所有元素都会前移,原来的迭代器就指向了一个错误的位置。
2. list中的迭代器失效
list 是双向链表,它的迭代器失效情况比 vector 要简单些。
list<int> myList = {1, 2, 3, 4, 5};
auto it = myList.begin();
++it; // it指向2
myList.erase(it); // 删除2,it失效
// 不能再使用it
对于 list,只有被删除节点的迭代器会失效,其他节点的迭代器仍然有效。这是因为 list 是链表结构,删除一个节点不会影响其他节点的内存位置。
3. map/set中的迭代器失效
map 和 set 是基于红黑树实现的,它们的迭代器失效规则和 list 类似。
map<int, string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}};
auto it = myMap.begin();
myMap.erase(it); // it失效
// 不能再使用it
同样,只有被删除元素的迭代器会失效,其他元素的迭代器仍然有效。
微信搜索 「跟着小康学编程」,关注我,后续还有更多硬核技术文章分享,带你玩转 Linux C/C++ 编程!
如何避免迭代器失效的坑?
知道了问题所在,我们该如何避免呢?这里有几个实用技巧:
技巧一:使用 erase 和 insert 的返回值
大多数容器的 erase 方法都会返回下一个有效迭代器,insert会返回指向新插入元素的迭代器,我们可以利用这一点。
// erase的返回值
vector<int> nums = {1, 2, 3, 4, 5};
for (auto it = nums.begin(); it != nums.end(); ) {
if (*it == 3) {
it = nums.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
// insert的返回值
vector<int> nums2 = {1, 2, 3, 4};
auto it2 = nums2.begin();
it2 = nums2.insert(it2 + 2, 100); // it2现在指向新插入的100
cout << *it2 << endl; // 输出100
这个技巧在需要连续操作容器时特别有用,可以保持迭代器始终有效。
技巧二:先记录再删除
vector<int> nums = {1, 2, 3, 4, 5};
vector<int> toRemove;
// 先标记要删除的元素
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == 3) {
toRemove.push_back(i);
}
}
// 从后往前删除(避免索引变化)
for (int i = toRemove.size() - 1; i >= 0; --i) {
nums.erase(nums.begin() + toRemove[i]);
}
技巧三:使用稳定的容器操作
一些容器操作不会导致迭代器失效,可以优先使用这些操作。
// 对于list,splice操作不会导致迭代器失效
list<int> myList = {1, 2, 3, 4, 5};
list<int> anotherList = {10, 20};
auto it = myList.begin();
++it; // it指向2
myList.splice(myList.end(), anotherList); // 不会导致it失效
cout << *it << endl; // 仍然是2
实战案例:解决常见迭代器失效问题
案例一:删除 vector 中的偶数
错误写法:
vector<int> nums = {1, 2, 3, 4, 5, 6};
for (auto it = nums.begin(); it != nums.end(); ++it) {
if (*it % 2 == 0) {
nums.erase(it); // 错误!迭代器失效
}
}
正确写法:
vector<int> nums = {1, 2, 3, 4, 5, 6};
for (auto it = nums.begin(); it != nums.end(); ) {
if (*it % 2 == 0) {
it = nums.erase(it);
} else {
++it;
}
}
案例二:在遍历的同时添加元素
错误写法:
vector<int> nums = {1, 2, 3};
for (auto it = nums.begin(); it != nums.end(); ++it) {
if (*it == 2) {
nums.push_back(4); // 错误!可能导致迭代器失效
}
}
正确写法(使用下标):
vector<int> nums = {1, 2, 3};
int size = nums.size(); // 先记录原始大小
for (int i = 0; i < size; ++i) {
if (nums[i] == 2) {
nums.push_back(4); // 使用索引而非迭代器
}
}
总结
迭代器失效看起来很复杂,但只要记住几个简单的规则,就能轻松避开这个坑:
- vector: 插入或删除元素后,该位置及其后面的迭代器都会失效;如果重新分配内存,所有迭代器都会失效。
- list/forward_list: 只有被删除元素的迭代器会失效。
- map/set/multimap/multiset: 只有被删除元素的迭代器会失效。
- unordered_map/unordered_set: 插入操作可能导致所有迭代器失效(rehash);删除操作只会导致被删除元素的迭代器失效。
实际编程中,优先考虑使用现代 C++ 的算法和容器操作,比如remove_if和erase的组合,往往能更优雅地解决问题:
vector<int> nums = {1, 2, 3, 4, 5, 6};
// 一行代码删除所有偶数
nums.erase(remove_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; }),
nums.end());
怎么样,迭代器失效这个坑,你现在是不是已经有底了?下次写代码的时候,别忘了提醒自己:容器变了,迭代器可能就不靠谱了!
踩坑不止于此,一起深度探索 C++!
看完了这篇文章,是不是感觉对迭代器失效有了全新的认识?其实 C++ 的坑远不止这一个,每一个坑背后都有精彩的技术原理和解决方案。
想要避开更多 C++ 开发中的隐藏陷阱,掌握那些让代码更高效、更优雅的技巧吗?欢迎关注我的公众号「跟着小康学编程」!
在这里,我会用同样接地气的语言,继续为你解锁:
- 那些让面试官眼前一亮的 C/C++ 核心知识
- 大厂实战中总结的性能优化秘诀
- 计算机基础知识的趣味解读
- 以及更多像"迭代器失效"这样的实战踩坑指南
学习编程就像破解谜题,每掌握一个知识点,都是打开新世界的一把钥匙。我希望能和你一起,把复杂的问题变简单,把枯燥的技术变有趣!
如果这篇文章对你有帮助,欢迎 点赞、收藏、关注,也欢迎在评论区分享你踩过的 C++ 坑!
下期见!
怎么关注我的公众号?
扫码即可关注。

哦对了,我还建了个技术交流群,大家一起聊技术、解答问题。卡壳了?不懂的地方?随时在群里提问!不只是我,群里还有一堆技术大佬随时准备帮你解惑。一起学,才有动力嘛!

迭代器失效:99%的C++程序员都会踩的坑 !的更多相关文章
- 千万小心,99%的Java程序员会踩这些坑
前言 作为Java程序员的你,不知道有没有踩过一些基础知识的坑. 有时候,某个bug查了半天,最后发现竟然是一个低级错误. 有时候,某些代码,这一批数据功能正常,但换了一批数据就出现异常了. 有时候, ...
- 听说,99% 的 Go 程序员都被 defer 坑过
原文链接: 听说,99% 的 Go 程序员都被 defer 坑过 先声明:我被坑过. 之前写 Go 专栏时,写过一篇文章:Go 专栏|错误处理:defer,panic 和 recover.有小伙伴留言 ...
- Python 程序员都会喜欢的 6 个库
在编程时,小挫折可能与大难题一样令人痛苦.没人希望在费劲心思之后,只是做到弹出消息窗口或是快速写入数据库.因此,程序员都会喜欢那些能够快速处理这些问题,同时长远来看也很健壮的解决方案. 下面这6个Py ...
- 程序员的踩坑经验总结(一):如何把Bug的偶现变必现
程序员的踩过的坑也是可以分类的,很常见又很难解决的一类是偶然的现象,表现起来比较怪异. 而把一个问题Bug的偶现变成必现,是开发人员的一种能力.我认为也应该是测试人员的一种能力,但是各个公司要求不一样 ...
- mysql优化专题」90%程序员都会忽略的增删改优化(2)
补充知识点:操作数据语句优化的认识 通常情况下,当访问某张表的时候,读取者首先必须获取该表的锁,如果有写入操作到达,那么写入者一直等待读取者完成操作(查询开始之后就不能中断,因此允许读取者完成操作). ...
- 每个程序员都会的 35 个 jQuery 小技巧
1. 禁止右键点击 $(document).ready(function(){ $(document).bind("contextmenu",function(e){ return ...
- 每个程序员都会的35个jQuery小技巧!
1. 禁止右键点击$(document).ready(function(){ $(document).bind("contextmenu",function(e){ return ...
- 程序员都会的 35 个 jQuery 小技巧
收集的35个 jQuery 小技巧/代码片段,可以帮你快速开发. 1. 禁止右键点击 $(document).ready(function(){ $(document).bind("cont ...
- Spring AOP注解为什么失效?90%Java程序员不知道
使用Spring Aop注解的时候,如@Transactional, @Cacheable等注解一般需要在类方法第一个入口的地方加,不然不会生效. 如下面几种场景 1.Controller直接调用Se ...
- dotNet程序员的Java爬坑之旅(一)
仔细想了下还是转java吧,因为后期不管是留在北京也好还是回老家也好,java的工作都会好找一点.现在的工作主要还是写.net,目标是下一次离职的时候可以找到一份全职的java工作,我一直都觉得实践才 ...
随机推荐
- DotNet跨平台 - docker+nginx+ssl 负载均衡
环境:CentOS7 服务器需要安装:docker.nginx.OpenSSL 一.部署方案 在linux服务器上,我们的Web站点程序采用docker容器部署,为了演示负载均衡,我们在同一台linu ...
- 从“技术宅”到"机器人教父",那个用机器人改变世界的年轻人
写在前面 随着民营企业座谈会的召开,有一位年轻的企业家王兴兴映入了我们的视野.没错就是那个让机器人从实验室走向舞台中央的年轻人. 大家对今年春晚的机器人扭秧歌应该都还印象深刻吧,它就出自于王兴兴创办的 ...
- ABB机械手维修37001电机开启接触器错误
当ABB机器人报告37001电机开启接触器错误时,这往往意味着电机上电的接触器在执行动作时遇到了障碍.具体而言,该错误通常与位于控制柜内左下角的接触器相关,其中K42和K43负责控制电机的开启操作.深 ...
- 微信企业付款到零钱(Java版)
订阅专栏1.开通条件. 商户注册超过90天且,连续30天有交易,可以每天支付1元来刷,目前测试可行.随后在微信商户平台 - 产品管理自动开启,然后需要申请. 疑问: 话说是要有公众号appid才行,但 ...
- 关于选择最佳.NET Core SSH服务器库的全面分析
关于选择最佳.NET Core SSH服务器库的全面分析 在安全远程访问领域,SSH(安全外壳协议)服务器对于在潜在不安全的网络上实现客户端与服务器之间的安全通信至关重要.对于使用.NET Core的 ...
- Java01 - Scanner对象
简介 之前我们学的基本语法中并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入.java.util.Scanner是Java5的新特征,我们可以通过Scanner ...
- 标题Spark Mavem项目在本地测试报错 : Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/spark/SparkConf
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/spark/SparkConf at S ...
- 基于Qt的在QGraphicsView中绘制带有可动拐点的连线的一种方法
摘要:本文详细介绍了基于Qt框架在`QGraphicsView`中实现带有可动拐点连线的绘制方法.通过自定义`CustomItem`和`CustomPath`类,结合`QGraphicsIte ...
- 强!10.8K star!推荐一款用于威胁预防、安全检测的开源监控平台,功能非常强大!!
在当今数字化时代,网络安全威胁日益严峻,企业和组织对于高效.强大的安全解决方案的需求也愈发迫切. 今天给大家分享一个专注于安全监控的开源项目:Wazuh,正是应对这一挑战的有力武器,专门用于帮助个人和 ...
- java的三大版本、特性和优势
Write Once Run AnyWhere 版本: JavaSE:标准版(桌面程序.控制台开发) JavaME:嵌入式开发(手机.家电...) JavaEE:E企业级开发(web端.服务端开发) ...