STL:从容器中删除元素的最佳实践

先吐槽一句

或许STL的设计者认为API的一致性很重要,函数名短而美很重要,人的脑容量不重要……

容器分类

  • 连续内存容器(vecotr、deque、queue、string)
  • list(没错,不一样的烟火就是你了)
  • 标准关联容器(set、multiset、map、multimap)

连续内存容器(vecotr、deque、queue、string)

首先明确两点:

  1. 这里的remove是指的std::remove函数,remove_if类似。而不是std::list::remove,后者后面会说到。
  2. 另外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;
        }
    }


posted @ 2020-04-15 16:04  joeyzzz  阅读(848)  评论(0编辑  收藏  举报