STL:从容器中删除元素的最佳实践
先吐槽一句
或许STL的设计者认为API的一致性很重要,函数名短而美很重要,人的脑容量不重要……
容器分类
- 连续内存容器(vecotr、deque、queue、string)
- list(没错,不一样的烟火就是你了)
- 标准关联容器(set、multiset、map、multimap)
连续内存容器(vecotr、deque、queue、string)
首先明确两点:
- 这里的remove是指
的std::remove函数,remove_if类似。而不是std::list::remove,后者后面会说到。 - 另外erase的删除单元素版本和删除区间版本都会返回被删除元素/区间的下一个元素的迭代器,因为erase会使后面位置的所有元素的迭代器失效。
无迭代删除:remove/remove_if配合erase
由于STL的算法模块设计是与具体的容器类型是解耦的,也就是说,它只接收各类容器的迭代器并操作,而迭代器又隐藏了容器中元素迭代的具体细节,所以std::remove无法真正的删除元素,因为它只知道容器的迭代器,根本不知道容器的erase方法。
对于连续内存容器,std::remove和std::remove_if的行为是把符合条件的元素全部移到容器的最末尾,并返回第一个待删除元素的迭代器。此时通过erase方法将这些元素都真正的删除掉即可。示例代码如下:
vector<int> vi{1,2,2,3,4,5,6,6};
vi.erase(remove(vi.begin(), vi.end(), 2), vi.end()); // 不使用erase的返回值
vi.erase(remove_if(vi.begin(), vi.end(), [](int i){ // 不使用erase的返回值
return i > 4;
}), vi.end());
有迭代删除:如果要删除一个元素时干其他事怎么办?
自然很容易联想到for配合erase来删除啦。但是对于连续内存容器,调用erase删除单个元素使后面元素的迭代器失效后(当然被删除那个也失效了),循环还在跑,对被删除的失效迭代器又自加了,行为未定义。所以要利用erase的返回值:
vector<int> vi{1,2,2,3,4,5,6,6};
for(auto iter = vi.begin(); iter != vi.end(); /*这里啥也不干*/)
{
if(*iter == 2)
{
iter = vi.erase(iter); //这里隐含了一次++
}
else
{
++iter;
}
}
list
list使用连续内存容器的区间删除和迭代删除方式都可以,但更为高效的是直接调用list::remove()函数,因为对于链表来说,把所有的待删除值扔后面再统一删除太浪费,链表删除单个元素是O(1)。
总感觉STL这里有点过度设计的意思……(逃
标准关联容器(set、multiset、map、multimap)
对标准关联容器的erase操作,会使当前删除的元素迭代器失效,而不影响其他元素。它的返回值是被删除的元素或者区间之后的第一个元素的迭代器。
他们都是基于红黑树的有序容器,如果要删除某一个键值对(假设键是唯一的),则直接调用erase传入键即可,如:
map<int, int> m{
{1, 2}, {2, 3}, {3, 4}
};
map.erase(1);
如果要在循环中删除多个元素,就要警惕迭代器失效的问题,我的解决思路和vector的删除差不多:
map<int, int> m
{
{1, 2}, {2, 3}, {3, 4}, {4,5}
};
for(auto iter = m.begin(); iter != m.end(); )
{
if(iter->second > 3)
{
iter = m.erase(iter);
}
else
{
++iter;
}
}