第9条:慎重选择删除元素的方法
先写总结,在写例子:
1、要删除容器中有特定值的所有对象:
如果容器是vector, string或deque,则使用erase-remove习惯用法
如果容器是list,则使用list::remove
如果容器是一个标准关联容器,则使用它的erase成员函数
2、要删除容器中满足特定判断式(条件)的所有对象:
如果容器是vector、string和deque,则使用erase-remove-if用法
如果容器是list, 则使用list::remove_if
如果容器是一个标准关联容器,则使用remove_copy_if和swap,或者写一个循环来遍历容器中的元素,当把迭代器传给erase时,要对它进行后缀递增。
3、要在循环内部做某些(除了删除对象之外的)操作:
如果容器是一个标准序列容器,则写一个循环来遍历容器中的元素,每次调用erase时,要用它的返回值更新迭代器
如果容器是一个标准关联容器,则写一个循环来遍历容器中的元素,每次迭代器传给erase时,要对它进行后缀递增
以下是例子:
假定一个容器c,包含int类型,Container<int> c;
问题1:当要删除c中所有值为1963的元素
(1)如果是一个连续内存容器(vectordeque string),使用erase-remove方法
remove操作并没有移除元素,而是将后面的元素覆盖删除元素,
返回新区间的逻辑终点,即下一个没有被删除的位置
(注意这里会使原来及其后面的迭代器失效,因为指向了不正确的元素)
erase删除两个迭代器之间的元素
c.erase(remove(c.begin(), c.end(), 1963),c.end());
(2)对于list,这一办法同样适用,但是适用list成员函数remove更高效
c.remove(1963);
(3)当c是标准关联容器(set multiset map multimap)时,不能使用remove操作,
因为没有这个成员函数,使用remove算法可能会覆盖容器的值,同时可能会破坏容器
正确方法是 调用erase
c.erase(1963); //只需要对数时间开销,序列容器需要线性时间
问题2:如果是使下面判断式返回true的每一个对象
bool badValue(int x); //返回x是否是“坏值”
(1) 对于序列容器(vectorstring deque list),对remove的调用换成remove_if就可以
c.erase(remove_if(c.begin(), c.end(),badValue), c.end());
list:c.remove_if(badValue)
(2)对于标准关联容器,解决方案有两种:
第一种:利用remove_copy_if把我们需要复制的值复制到一个新容器中,然后把原来容器的内容和新容器的内容相互交换:
AssocContainer<int> c; //c是标准关联容器
…
AssocContainer<int> goodValue; //保存不被删除的值的临时容器
remove_copy_if(c.begin(), c.end(), //把不被删除的值从c复制到goodValues中
inserter(goodValues,goodValues.end()), badValue);
c.swap(goodValue); //交换c和goodValues的值
缺点是:需要复制所有不被删除的元素
不能直接从原始的容器中删除元素,因为容器没有提供类似remove_if的成员函数。
第二种:可以写一个循环,在遍历过程中删除元素。
一个直截了当的方法:
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); ++i )
if(badValue(*i))
c.erase(i);
但是这样会导致不确定的行为,当容器中的一个元素被删除时,指向该元素的所有迭代器都变得无效,c.erase(i)之后,i变为无效值。
为了避免这个问题,要确保在调用erase之前,有一个迭代器指向c中的下一个元素。
最简单的方法是,对i使用后缀++
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); (什么都不做) )
if(badValue(*i))
c.erase(i++); //这样在执行erase之前已经对i递增为新的值,旧的值删除
//变为了无效值,但i已经成为新值
else
++i;
问题3:如果我们不仅要删除使badValue返回true的元素,还当元素被删除时向日志文件记录一条信息
ofstream logFile;
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); (什么都不做) )
if(badValue(*i)) {
logFile << “Eraseing” << *i << ‘\n’; //写日志文件
c.erase(i++); //关联容器对erase的返回值是void
}
else
++i;
这种方法对vector string deque无效,会导致不确定行为,因为这种容器调用erase不仅会使指向被删除元素的迭代器无效,也会使被删除元素之后的所有迭代器失效(因为容器位置变了,原来指向的内容已经变了),++i,--i都会无效。
所以对于vector string deque,利用erase的返回值。返回值是我们所需要的,一旦erase完成,它是指向紧随被删除元素的下一个元素的有效迭代器。
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); (什么都不做) )
if(badValue(*i)) {
logFile << “Eraseing” << *i << ‘\n’; //写日志文件
i =c.erase(i);
}
else
++i;
对于list来说,一般是对list采用和vector string deque相同的方式。