删除某个值
假设我们有一个容器,其内部元素类型为int.现在需要删除容器c内所有值为1963的元素,值得注意的是,完成这项任务的方法因不同的容器类型而不同,无通用解。
连续内存容器(vector,deque,string)
最好的删除操作是erase—remove惯用法:1
c.erase(remove(c.begin(),c.end(),1963),c.end());
list
list也可以用erase_remove,但是最好直接使用remove:1
c.remove(1963);
标准关联容器
标准关联容器没有remove成员函数,其删除操作的正确做法是使用erase1
c.erase(1963);
这种做法兼具高效性与正确性,对数时间开销,同时关联容器的erase基于等价而非相等。(详见Effective STL 19)
删除所有符合条件的值
序列容器(连续内存容器与list)
我们只需要把remove改为remove_if1
2
3bool badValue(int);
c.erase(remove_if(c.begin(),c.end(),badValue),c.end());//连续内存容器
c.remove_if(badValue); //list
关联容器
对于关联容器,一般有两种方法来处理这一情况,一个容易编码,一个高效。
容易编码的方法
其原理为:remove_copy_if把我们需要的值拷贝到新容器中,然后把原容器内容与新容器的交换:1
2
3
4
5AssocContainer<int> c;
...
AssocContainer<int> goodValues; //临时容器
remove_copy_if(c.begin(), c.end(), inserter(goodValues, goodValues.end()), badValue);
c.swap(goodValues); // 交换c和goodValues
这种方法的缺点是它拷贝了所有不删除的元素,直接造成了效率极低。
高效的方法
其直接从容器中删除元素,不过关联容器没有remove_if之类的成员函数,所以必须通过循环来完成,首先给出一个错误案例:1
2
3
4
5AssocContainer<int> c;
...
for (auto i = c.begin(); i!= c.end(); ++i) {
if (badValue(*i)) c.erase(i);//未考虑迭代器失效
}
为了避免迭代器失效,我们必须保证在调用erase之前就得到了c中下一个元素的迭代器,所以有正确写法如下:1
2
3
4
5
6AssocContainer<int> c;
...
for (auto i = c.begin(); i != c.end();/*nothing*/ ){
if (badValue(*i)) c.erase(i++); //如果需要删除,删除当前位置并自增迭代器
else ++i;//不需要删除则直接递增
}
删除元素后记录日志
关联容器
对于关联容器,只要在循环内部增加一条语句即可:1
2
3
4
5
6
7
8
9
10ofstream logFile; //日志文件
AssocContainer<int> c;
...
for (auto i = c.begin();i !=c.end();){
if (badValue(*i)){
logFile << "Erasing " << *i <<'\n'; //写日志文件
c.erase(i++); // 删除元素
}
else ++i;
}
连续内存容器
与关联容器不同,连续容器的erase不仅令当前迭代器失效,也同时令后面的迭代器失效。所以我们必须利用erase返回被删除元素之后的元素的迭代器的特性,完成操作:1
2
3
4
5
6
7for (auto i = c.begin();i != c.end();){
if (badValue(*i)){
logFile << "Erasing " << *i << '\n';
i = c.erase(i);//利用返回值,也是C++ primer描述容器删除时使用的方法
}
else ++i;
}
list
对于list,上述两种方法都行。
总结:
- 去除容器内有特定值的对象,连续容器使用erase-remove,list使用remove,关联容器使用erase
- 去除满足某个判定式的所有对象,连续容器使用erase-remove_if,list使用remove_if,关联容器则使用remove_cooy_if与swap,或者使用循环(记得后置递增)
- 需要在循环中做操作时,连续内存容器记得要利用erase返回值更新迭代器,关联容器同2。