概述
一个合格的容器(Container)必须支持增删改查,C++的顺序容器和关联容器也不例外。
不同于insert和search,erase操作涉及到多种情况,尤其是和迭代器相结合的时候。
本文总结常用C++常用容器的erase的正确做法。
- 顺序容器:vector,dequeue,list,string
- 关联容器:set,map,unordered_set, unordered_map
顺序容器
vector
vector是STL中唯一一个保证内存连续的容器,也是我们最常用的容器。
删除值为val的元素
void EraseVec()
{
std::vector<int> vec{ 1, 2, 3, 4, 5, 5, 6, 7 };
vec.erase(std::remove(vec.begin(), vec.end(), 5), vec.end());
}
上述代码是最标准的在vector中删除值为5的元素的方法,你会发现有两个很容易让人产生误会的接口:
erase和remove,我们看一下他们的原型:
iterator erase (iterator begin, iterator end);
//tips : 删除容器中迭代器在[first; last)范围内的元素
//return : 最后一个被删除的元素的下一个元素的迭代器
iterator remove(iterator begin, iterator end, val);
//tips : 移除容器中迭代器在[first; last)范围内的值为val的元素
//return : 被成功移除的之后,剩余的有效元素的下一个元素的迭代器
我们可以看到,remove做的操作是移除而erase是删除,这两者到底有什么区别呢?
我们刚创建的vec的内存布局如下:
在remove(begin,end,5)之后,内存布局如下:
它仅仅做了移除操作,把不要的元素放在了末尾(这是其中一种实现,你不能对67后面的两个位置的值做出任何假设),但是capacity甚至size都没有改变,也就是说元素并没有被删除。
更可怕的是,如果你做了下列操作:
std::remove(vec.begin(), vec.end(), 5);
for (auto it = vec.begin(); it != vec.end(); ++it)
{
printf("%d\n", *it);
}
你会发现输出的值是12346755(或者可能是132456767),元素根本没有被删除
在vec.erase(std::find(vec.begin(), vec.end(), 5))
操作之后,内存布局:
元素被真正的删除了。
再次强调一下最正确的删除方式:
void EraseVec()
{
std::vector<int> vec{ 1, 2, 3, 4, 5, 5, 6, 7 };
vec.erase(std::remove(vec.begin(), vec.end(), 5), vec.end());
}
删除满足某个条件的元素
void EraseVecIf()
{
std::vector<int> vec{ 1, 2, 3, 4, 5, 5, 6, 7 };
vec.erase(std::remove_if(vec.begin(), vec.end(),
[](int elem)
{
return elem % 2 == 0;
}),
vec.end());
}
循环删除
我们再看一种比较容易犯错的情况,那就是在循环种删除某个迭代器:
void EraseVecLoopError()
{
std::vector<int> vec{ 1, 2, 3, 4, 5, 5, 6, 7 };
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.erase(it);
}
}
}
这里是错误的示范,为何呢?
原因在于,vec.erase(it)
会让vec中的迭代器失效,直接it++会让程序崩溃,所以这里正确的做法应该是合理利用erase的返回值:
void EraseVecLoopCorrect()
{
std::vector<int> vec{ 1, 2, 3, 4, 5, 5, 6, 7 };
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); /*do nothing*/)
{
if (*it % 2 == 0)
{
it = vec.erase(it);
}
else
{
it++;
}
}
}
deque
deque和vec的方法完全相同,不再赘述
string
string和vec的方法完全相同,不再赘述
list
list和vector、dequeue、string不同,它的删除更为简单:
void EraseList()
{
std::list<int> ls{ 1, 2, 3, 4, 5, 5, 6, 7 };
ls.remove(5);
ls.remove_if([](int elem) { return elem % 2 == 0; });
}
对于list,直接使用remove方法即可。(对于list而言移除 == 删除)
当然,你仍可以使用erase接口,只不过remove方法更为效率一点。
循环删除的方式
void EraseListLoop()
{
std::list<int> ls{ 1, 2, 3, 4, 5, 5, 6, 7 };
for (std::list<int>::iterator it = ls.begin(); it != ls.end(); )
{
if (*it % 2 == 0)
{
it = ls.erase(it);
}
else
{
it++;
}
}
}
关联容器
map和unordered_map
由于map和unordered_map的特殊性(他们的元素都是一个pair),C++没有提供这样的接口
int x = 5;
container.erase(x)
container.remove(x)
这样的接口,如果想完成类似的需求那么只能:
void EraseSet()
{
std::map<int, int> m;
m.insert(std::make_pair(1, 1));
m.insert(std::make_pair(2, 2));
m.insert(std::make_pair(3, 3));
m.insert(std::make_pair(4, 4));
m.insert(std::make_pair(5, 5));
m.insert(std::make_pair(6, 6));
m.insert(std::make_pair(7, 7));
map<int,int>::iterator it = m.find(5);
if (it != m.end())
{
m.erase(it);
}
}
这个是比较符合我们的直觉的,但是如果在循环种删除满足某种条件的迭代器,那么就需要好好注意了:
void EraseMapError()
{
std::map<int, int> m;
m.insert(std::make_pair(1, 1));
m.insert(std::make_pair(2, 2));
m.insert(std::make_pair(3, 3));
m.insert(std::make_pair(4, 4));
m.insert(std::make_pair(5, 5));
m.insert(std::make_pair(6, 6));
m.insert(std::make_pair(7, 7));
//1.error
for (std::map<int, int>::iterator it = m.begin(); it != m.end(); it++)
{
if (it->second % 2 == 0)
{
m.erase(it);
}
}
}
处于同样(见上文,vector:循环删除)的原因,我们不能再循环种直接it++,可是对于关联容器,erase这个方法没有返回iterator,所以正确的做法是:
void EraseMapCorrect()
{
std::map<int, int> m;
m.insert(std::make_pair(1, 1));
m.insert(std::make_pair(2, 2));
m.insert(std::make_pair(3, 3));
m.insert(std::make_pair(4, 4));
m.insert(std::make_pair(5, 5));
m.insert(std::make_pair(6, 6));
m.insert(std::make_pair(7, 7));
//2.right
for (std::map<int, int>::iterator it = m.begin(); it != m.end(); )
{
if (it->second % 2 == 0)
{
//it是被删除的迭代器,it++的副作用可以获得it指向的下一个元素
m.erase(it++);
}
else
{
it++;
}
}
}
set和unordered_set
set和unordered_set可以直接使用erase方法,但是remove不起作用,使用remove会编译错误:
void EraseSet()
{
std::set<int> s;
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(4);
s.insert(5);
s.insert(6);
s.erase(5);
}
循环删除则同map和unordered_map,不再赘述。