迭代器失效问题

匿名 (未验证) 提交于 2019-12-03 00:32:02

在前面我们实现了vector和list
不管是库里面的使用
还是我们自己模拟实现的版本
今天来看一个关于容器容易出现的问题,迭代器失效问题
如果我们有如下要求,在一个list中,我们要求只将值为偶数的节点删除掉。
我们就实现如下代码:

void test_iterator_invalid() {   list<int> L;   L.push_back(1);   L.push_back(2);   L.push_back(3);   L.push_back(4);   L.push_back(5);   //利用模板数打印list中的内容   PrintContainers(L);    //要求只是删除值为偶数的节点    list<int> ::iterator it1=L.begin();   while(it1!=L.end())   {     if(*it1%2==0)//为偶数     {       L.erase(it1);     }     it1++;   }   cout<<endl;   printf("删除值为偶数的节点\n");   PrintContainers(L);  } 

上面的代码 我们看上去没有什么问题,但是运行之后会崩掉
此处不上传崩溃的截图
分析原因:

list的迭代器失效是因为在进行erase()之后,节点被释放,但是当前迭代器还是指向该节点,node 节点已经被释放,node->next和node->prev都指向一块随机地址,我们也成为野指针,是不允许访问的。但是在循环中进行++操作时,其实是执行了node=node->next,访问非法地址,程序会崩掉。
   //要求只是删除值为偶数的节点    list<int> ::iterator it2=L.begin();    while(it2!=L.end())   {     if(*it2%2==0)     {//保存当前要删除的位置       list<int> ::iterator to_delete=it2;       it2++;//先向后走       L.erase(to_delete);//再删除该节点     }     else     {       it2++;     }   }

上面这种方法是我们之前删除链表节点的方法,根据上一篇我们模拟库里面实现的 MyList 我们实现的Erase()的返回值也是迭代器,原理上来说,实现一个list 的erase(),我们是不需要返回值的 ,其实这里的返回值就是作用于这个地方,解决迭代器失效的问题的。
模拟实现list

利用erase()函数的返回值为iterator,返回的是删除节点的下一个位置的迭代器,此时我们就不用将迭代器继续向后走

list<int>::iterator it3=L.begin();   while(it3!=L.end())   {     if(*it3%2==0)     {       it3=L.erase(it3);       //用it3接收erase()的返回值,就不用向后++     }     else     {       it3++;     }   } 

总结list中迭代器失效:
链表删除节点导致的迭代器失效,容易造成访问野指针的问题,导致程序崩溃

同样上面的问题
我们需要删除顺序表中值为偶数的元素
我们可以很快的写下如下代码:

void test_vector_iterator_invalid() {   vector<int> v1;   v1.push_back(2);   v1.push_back(5);   v1.push_back(3);   v1.push_back(1);   v1.push_back(4);    //用模板函数来实现打印顺序表   PrintContainers(v1);    //实现值删除顺序表中值为偶数的元素   vector<int> ::iterator it1=v1.begin();   while(it1!=v1.end())   {     if(*it1%2==0)     {       v1.erase(it1);     }      it1++;   }   PrintContainers(v1);  } 

猜猜看程序会不会崩掉,,,,,
算了还是先来用图分析一波

我们看到 it++之后机会 直接越过了元素6,会导致少判断一个元素,造成逻辑错误,这样的结果并不是我们想要的,但是这里并没有访存出错的问题,我们猜想程序应该不会崩掉吧
结果是程序依旧崩掉了。
因为,但我们在进行erase()时,编译器内部是知道会存在这样的问题,所以它内部机制了如果用到erase()就必须接受返回值,否则就触发错误,也是为了防止迭代器失效的问题发生。
vector中实现erase()返回值也是迭代器,参考模拟实现vector

vector<int> ::iterator it2=v1.begin();   while(it2!=v1.end())   {     if(*it2%2==0)     {       it2=v1.erase(it2);//用it2接收erase()的返回值,it2不进行向后走     }     else     {       it2++;     }   }   PrintContainers(v1);

那么这里是否可以向解决list 迭代器失效的方案一一样,先将此处迭代器保存下来,再进行++,然后erase(),我们说是不行的

我们分析,当将 it1 先进行++后,it1就指向了下一个位置的,当进行erase()之后,it1 仍旧是在它的位置上,它里面的值也并不是我们期待的

为什么vector的插入会导致迭代器的失效?我们来看如下场景

void test_vector_iterator_invalid() {   vector<int> v1;   v1.reserve(5);   v1.push_back(1);   v1.push_back(2);   v1.push_back(6);   v1.push_back(4);   v1.push_back(5);    //用模板函数来实现打印顺序表   PrintContainers(v1);   cout<<"Size:"<<v1.size()<<endl;   cout<<"Capacity:"<<v1.capacity()<<endl;    vector<int>:: iterator it3=--(v1.end());    v1.insert(it3,8);//在最后一个元素之前插入8   it3++;//对it3++   cout<<*it3<<endl;   PrintContainers(v1);  } 

这里的演示结果在VS下观察得到。linux下的编译器会优化处理掉这个错误


这里进行图片解释程序崩溃的原因:

文章来源: 迭代器失效问题
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!