STL容器中元素的删除的惯用法

来自《编写高质量代码:改善C++程序的150个建议》读书笔记

vector<int> intVec{ 1,2,3,4,5 };
for (vector<int>::iterator itr = intVec.begin(); itr != intVec.end(); ++itr)
{
    if ((*itr) == 3)
        intVec.erase(itr);
}

以上代码在删除时,当intVec.erase(itr) 返回时,itr已经失效了,在for循环中对于失效的itr执行自增操作就崩溃了。

尝试借助remove算法来实现:

vector<int> intVec{ 1,2,3,4,5 };
size_t before_size = intVec.size();
remove(intVec.begin(), intVec.end(), 3);
size_t after_size = intVec.size();

前后size没有变化,也就是说remove没有改变容器中元素的个数。在remove操作后,intVec内的数据出现了诡异的变化,intVec内数据排列成1,2,4,5,5。
remove只会将不应该删除的元素前移,然后返回一个迭代器,该迭代器指向的是那个应该删除的元素,所以如果要真正删除这一元素,在调用remove之后还必须调用erase,这就是STL容器元素删除的“erase-remove”的惯用法。

intVec.erase(remove(intVec.begin(), intVec.end(), 3));

erase-remove的惯用法适用于连续内存容器,比如vector、deque和string,它同样适合list,但并不推荐,使用list的成员函数remove会更高效:

list<int> intList;
intList.remove(3);

对于标准关联容器,标准关联容器没有remove成员函数,使用STL算法的remove函数时编译不通过,所以,在这种情况下,解决办法是调用erase:

map<int,int> mapContainer;
...
mapContainer.erase(25);

对于标准关联容器,这样的元素删除方式简单而高效,时间复杂度为O(logn),即对数时间。

如果我们的需求不再是删除某一个具有特定值的元素,而是删除具备某一条件的一些元素时,比如,要删除容器中所有小于25的元素,针对那些使用remove的容器,我们只需将remove替换成remove_if即可:

bool Is2BeRemoved(int value);
// container如果是vector、deque、string或者list
container.erase(remove_if(container.begin(), container.end(), Is2BeRemoved), container.end());
// container是list最佳选择
container.remove_if(Is2BeRemoved);

对于标准关联容器,采用的则是remove_copy_if&swap组合的解决方式。但是由于涉及容器内容的交换,这种方式虽然简洁容易,但是效率相对较低。在通过remove_copy_if 按照条件拷贝了需要的元素之后,如何实现两个map的交换,可以直接调用map的成员函数swap:

#include <map>
#include <algorithm>
#include <iterator>
bool notCopy(pair<string, int> key_value) {
    return key_value.second == 1;
}
int main()
{
    map<string, int> mapCount;
    mapCount.insert(make_pair("000", 0));
    mapCount.insert(make_pair("001", 1));
    mapCount.insert(make_pair("002", 2));
    mapCount.insert(make_pair("003", 1));

    map<string, int> mapCountTemp;//临时map容器
    //之所以要用inserter()函数是因为通过调用insert()成员函数来插入元素,并由用户指定插入位置
    remove_copy_if(mapCount.begin(), mapCount.end(), inserter(mapCountTemp, mapCountTemp.begin()), notCopy);

    mapCount.swap(mapCountTemp);//实现两个容器的交换

    cout << mapCount.size() << endl;     //输出2
    cout << mapCountTemp.size() << endl; //输出4

    //验证
    //正确输出:
    //000 0
    //002 2
    for (map<string, int>::iterator it = mapCount.begin(); it != mapCount.end(); ++it) {
        cout << it->first << " " << it->second << endl;
    }
    return 0;
}

(本段参考:http://blog.csdn.net/scythe666/article/details/51285548

关联容器和序列容器不一样,序列容器插入和删除操作都可能会使容器的部分或全部迭代器失效,因为vector和deque必须使用连续分配的内存来存储元素,向容器添加一个元素可能会导致后面邻接的内存没有可用空间而引起存储空间的重新分配,一旦发生这种情况,容器钟所有的迭代器都会全部失效。而当删除当前的iterator则会使后面所有元素的iterator都失效,这因为删除一个元素会导致后面所有的元素都向前移动一个位置。
对于关联容器(map、set、multimap和multiset),删除当前iterator,仅仅会使当前的iterator失效,其影响范围不像序列容器那样大,这是因为关联容器存储元素所需的内存不是连续的。比如map之类的容器是用红黑书实现,在插入、删除一个结点不会对其他的结点造成影响。所以在删除元素时,只要在erase后递增当前的iterator,使当前的iterator指向下一个有效结点即可。

猜你喜欢

转载自blog.csdn.net/zuolj/article/details/78435062