[C++]顺序容器和关联容器的删除

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sixdaycoder/article/details/81510877

概述

一个合格的容器(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,不再赘述。

猜你喜欢

转载自blog.csdn.net/sixdaycoder/article/details/81510877
今日推荐