C++ STL 迭代器失效问题的剖析

来源:互联网 发布:ibm软件的数据仓库 编辑:程序博客网 时间:2024/06/06 00:03
STL中容器分为顺序容器和关联容器,顺序容器有:vector、deque、list;关联容器有:set、map、multiset、multimap。
一、vector、deque、list容器
vector、deque、list 中遍历删除某些元素时可以使用下面方式
(1)正确用法1
通过erase方法的返回值来获取下一个元素的位置。
std::vector<int> Vec;std::vector<int>::iterator itVec;for(itVec = Vec.begin(); itVec != Vec.end(); ){    if (WillDeleteCondition(*itVec))        itVec = Vec.erase(itVec);    else        itVec++;}
std::list<int> List;std::list<int>::iterator itList;for(itList = List.begin(); itList != List.end(); ){    if (WillDeleteCondition(*itList))        itList = List.erase(itList);    else        itList++;}
(2)对于list也可以使用下列方法
在调用erase方法之前先使用"++"来获取下一个元素的位置
std::list<int> List;std::list<int>::iterator itList;for(itList = List.begin(); itList != List.end(); ){    if (WillDeleteCondition(*itList))        List.erase(itList++);    else        itList++;}
(3)错误用法
在调用erase方法之后使用"++"来获取下一个元素的位置,由于在调用erase方法之后,该元素的位置已经被删除,如果再根据这个旧的位置来获取下一个位置,则会出现异常。
std::list<int> List;std::list<int>::iterator itList;for(itList = List.begin(); itList != List.end(); itList++){    if (WillDeleteCondition(*itList))        List.erase(itList);}
二、set、map容器
set、map中遍历删除某些元素时可以使用下面方式
(1)正确写法A
std::map<int, int> Mapstd::map<int, int>::iterator itMapfor(itMap = Map.begin(); itMap != Map.end(); ){    if (WillDeleteCondition(itMap->second))        Map.erase(itMap++);    else        itMap++;}
三、剖析原因
C++标准中,顺序容器的erase函数会返回iterator,但关联容器的erase函数不返回iterator。
(1)对于顺序容器 vector、deque,删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector、deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。erase方法可以返回下一个有效的iterator。【即正确用法1】
(2)对于关联容器map、set、multimap、multiset,删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。【即正确用法A】
(3)对于顺序容器list,erase方法可以返回下一个有效的iterator【即正确用法1】。由于list是一个链表,删除当前的iterator,仅仅会使当前的iterator失效,所以也可以在erase时,递增当前的iterator【即正确用法A】。
(4)erase函数返回被删除元素的下一个元素的迭代器。在STL中,不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的
四、迭代器失效的情况
(1)vector
内部数据结构:数组
随机访问每个元素,所需要的时间为常量。
在末尾add/delete元素所需时间与元素数目无关,在开头或中间add/delete元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。
add元素:所有元素的迭代器失效(内存重新分配时)(当把超过capacity()-size()个元素插入vector中时,内存会重新分配)
               当前元素以后的任何元素的迭代器失效(内存没有重新分配时)
delete元素:被删除元素以后的任何元素的迭代器都将失效
(2)deque
内部数据结构:数组
随机访问每个元素,所需要的时间为常量
在开头或末尾add/delete元素所需时间与元素数目无关,在中间add/delete元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,不提供用于内存管理的成员函数。
add元素:迭代器失效
delete元素:迭代器失效(删除中间元素)
                    指向该元素的迭代器失效(删除头/尾元素)
(3)list
内部数据结构:双向环状链表
不能随机访问一个元素,可双向遍历。
在开头/中间/末尾add/delete元素所需时间都为常量
可动态增加或减少元素,内存管理自动完成。
add元素:不会使迭代器失效
delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效
(4)set
内部数据结构:红黑树
键和值相等,键唯一,元素默认按升序排列。
add元素:不会使迭代器失效
delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效
(5)map
内部数据结构:红黑树
键唯一,元素默认按键的升序排列。
add元素:不会使迭代器失效
delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效
五、实例
// iteratorInvalidateVector.cpp#include <iostream>#include <vector>typedef std::vector<int> Vector;typedef std::vector<int>::iterator VectorIt;void printLog(Vector vectOne){    VectorIt it;    for (it = vectOne.begin(); it != vectOne.end(); it++)    {        std::cout<< *it<<" ";    }    std::cout<<std::endl;}void deleteValue(Vector &vectOne, int n){    VectorIt it;    for (it = vectOne.begin(); it != vectOne.end(); )    {        if (0 == (*it % n))            it = vectOne.erase(it);        else            it++;    }}int main(){    Vector vectOne;    int i,j = 0;    for(i = 0; i < 21; i++)    {        j = i;        if (i == 3)            j = i+1;        vectOne.push_back(j);    }    printLog(vectOne);    deleteValue(vectOne, 2);    printLog(vectOne);    return 0;}
//iteratorInvalidateList.cpp#include <iostream>   #include <list>   typedef std::list<int> List;typedef std::list<int>::iterator ListIt;void printLog(List listOne){    ListIt it;    std::cout<<std::endl;    for (it = listOne.begin(); it != listOne.end(); it++)           std::cout << *it << " ";       std::cout << std::endl;}void deleteValue(List &listOne, int n){    ListIt it;    for (it = listOne.begin(); it != listOne.end(); )    {        if (0 == (*it % n))            //it = listOne.erase(it);            listOne.erase(it++);        else            it++;    }}int main()   {       List listOne;    int i, j = 0;    for(i = 0; i < 21; i++)    {        j = i;        if (i == 3)            j = i+1;        listOne.push_back(j);    }    printLog(listOne);    deleteValue(listOne, 2);    printLog(listOne);    return 0;} 
// iteratorInvalidateMap.cpp#include <iostream>#include <map>#include <vector>typedef std::map<int, int> Map;typedef std::map<int, int>::iterator MapIt;void printLog(Map m){    MapIt it;    for (it = m.begin(); it != m.end(); it++)    {        std::cout<< it->second <<" ";    }    std::cout<<std::endl;}void deleteValue(Map &m, int n){    MapIt it;    for (it = m.begin(); it != m.end(); )    {        if (0 == (it->second % 2))        {            m.erase(it++);        }        else        {            it++;        }    }}void deleteValueOne(Map &m, int n){    MapIt it;    for (it = m.begin(); it != m.end(); it++)    {        if (0 == (it->second % 2))            m.erase(it);    }}int main(){    Map m;    int i = 0;    for(i = 0; i < 21; i++)    {        m[i] = i;    }    m[3] = 4;    printLog(m);    deleteValue(m, 5);    printLog(m);    return 0;}
注意:在iteratorInvalidateMap.cpp 中如果使用函数deleteValueOne(),并不会造成crash,但没有得到正确的结果,得到的结果如下
0 1 2 4 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 4 5 7 9 11 13 15 17 19 
因为(it指向2时)在执行m.erase(it)之后,it自动绑定到下一个元素(第一个4),此时又执行for循环中的it++(it指向第二个4),就跳过了map中的第一个4。







原创粉丝点击