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 ;
}

  当 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 + != end())
copy(position + , 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> 第九条,较详细的讨论的各种容器的操作方法,有兴趣的读者可自行翻阅。

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

 

  

  

  

c++之迭代器失效的更多相关文章

  1. C++ STL 迭代器失效问题

    之前看<C++ Primier>的时候,也解到在顺序型窗口里insert/erase会涉及到迭代器失效的问题,并没有深究.今天写程序的时候遇到了这个问题. 1 莫名其妙的Erase 最初我 ...

  2. STL的erase()陷阱-迭代器失效总结

    下面材料整理自Internet&著作. STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector .deque):另一类是以不连续的节点形式存储的容器(如:list.s ...

  3. C++ Primer : 第九章 : 顺序容器的操作以及迭代器失效问题

    顺序容器的添加.访问.删除操作以及forward_list的特殊操作,还有迭代器失效问题. 一.向容器添加元素 // array不支持这些操作 // forward_list有自己撰于的版本的inse ...

  4. C++ STL中迭代器失效的问题

    my_container.erase(iter); 其中my_container是STL的某种容器,iter是指向这个容器中某个元素的迭代器.如果不是在for,while循环中,这种方式删除元素没有问 ...

  5. 容器大小的改变以及容器操作可能使迭代器失效、vector对象的容量变化

    1 改变容器的大小 我们可以使用resize来增加或缩小容器,与往常一样,array不支持resize.如果当前大小大于所要求的大小,容器后面的元素会被删除:如果当前大小小于新大小,会将新元素添加到容 ...

  6. vector迭代器失效的一种情形

    使用过STL的人都应该知道关于迭代器失效的原理,这里以后vector迭代器失效为例: 第一种:当插入一个元素到vector中,如果插入后容器已满,那么容器将新开辟一块内存区域,然后 将原内存中的数据拷 ...

  7. STL源代码分析--迭代摘要、迭代器失效汇总

    Vector 1.内部数据结构:连续存储,比如数组. 2.随机訪问每一个元素,所须要的时间为常量. 3.在末尾添加或删除元素所需时间与元素数目无关,在中间或开头添加或删除元素所需时间随元素数目呈线性变 ...

  8. Iterator invalidation(迭代器失效)

    一.vector 所有读操作.swap.std::swap:都不会引起迭代器失效... clear.operator=.assign:都会引起全部变量迭代器失效 reserve.shrink_to_f ...

  9. 为什么对string调用swap会导致迭代器失效

    一般来说,swap操作将容器内容交换不会导致容器的指针.引用.迭代器失效. 但当容器类型为array和string时除外. 原因在于:SSO  (Short String Optimization 指 ...

  10. 谈谈知识的融会贯通:以“java中的迭代器失效问题”为例

    提示 文中涉及知识点: Collection . Iterator Guava 中的 Lists.partition 方法 如果你对这两个知识点不了解,强烈建议阅读文中引用的参考文章. 场景一:以Ar ...

随机推荐

  1. 【转】通过ionice和nice降低shell脚本运行的优先级

    对于一些运行时会造成系统满载的脚本, 例如数据库备份, 会影响当时其他服务的响应速度, 可以通过ionice和nice对其IO优先级和CPU优先级进行调整例如降低"/usr/local/bi ...

  2. Cassandra go语言client使用

    关于什么是cassandra,可以参考: http://blog.csdn.net/zyz511919766/article/details/38683219 http://cassandra.apa ...

  3. 15.Update Documents-官方文档摘录

    1.插入数据 db.inventory.insertMany( [ { item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: ...

  4. OCR技术浅探: 语言模型和综合评估(4)

    语言模型 由于图像质量等原因,性能再好的识别模型,都会有识别错误的可能性,为了减少识别错误率,可以将识别问题跟统计语言模型结合起来,通过动态规划的方法给出最优的识别结果.这是改进OCR识别效果的重要方 ...

  5. 看我学习Apache+php+wordpress+phpMyAdmin的搭配配置

    开场白:我不是这方面的"专家"或"菜鸟",因为我不懂,别问我为什么,我只是心血来潮好奇,东拼西凑写了这些文字. 1.php的配,使用免安装版本,要进行的设置, ...

  6. 从数学分析的角度来看Softmax

    作者:无影随想 时间:2016年1月. 出处:https://zhaokv.com/machine_learning/2016/01/softmax-calculous-perspective.htm ...

  7. SDUT2857:艺术联合会(简单dp)

    链接: http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2857 题目解析: 这是去年校赛的题目, ...

  8. 过滤adb logcat 日志

    原文地址http://blog.csdn.net/listening_music/article/details/7518990 另外比较好的文章http://blog.csdn.net/liao27 ...

  9. [C语言](*p)++ 与 *p++ 与 ++*p 拨开一团迷雾

    环境:win7 IDE:DEV-C++ 编译器:GCC 1.先说++i和i++的基础 代码如下: #include <stdio.h> //just change simple void ...

  10. node的3大作用域

    除了持久性存储外,想要内存也可以存入数据,来做计算什么数据都存入访问一便数据库,效率就太低了 java有3大作用域request 指在一次请求的全过程中有效,即从http请求到服务器处理结束,返回响应 ...