容器删除元素的方法(备份csdn博客,防止删除)

Effective STL 第一章 第9条
1、要删除容器中有特定值的所有对象;

2、要删除容器中满足特定判断式(条件)的所有对象;

3、要在循环内部做某些(除了删除对象之外的)操作。

完全复制粘贴自https://blog.csdn.net/xlf13872135090/article/details/18040955,该博客总结至《Effective STL 第一章 第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 c;

问题1:当要删除c中所有值为1963的元素

(1)如果是一个连续内存容器(vectordeque string),使用erase-remove方法

remove操作并没有移除元素,而是将后面的元素覆盖删除元素,

返回新区间的逻辑终点,即下一个没有被删除的位置

(注意这里会使原来及其后面的迭代器失效,因为指向了不正确的元素)

erase删除两个迭代器之间的元素

1
c.erase(remove(c.begin(), c.end(), 1963),c.end());

(2)对于list,这一办法同样适用,但是适用list成员函数remove更高效

1
c.remove(1963);

(3)当c是标准关联容器(set multiset map multimap)时,不能使用remove操作,

因为没有这个成员函数,使用remove算法可能会覆盖容器的值,同时可能会破坏容器

正确方法是 调用erase

1
c.erase(1963);    //只需要对数时间开销,序列容器需要线性时间

问题2:如果是使下面判断式返回true的每一个对象

1
bool badValue(int x);  //返回x是否是“坏值”

(1) 对于序列容器(vectorstring deque list),对remove的调用换成remove_if就可以

1
2
3
c.erase(remove_if(c.begin(), c.end(),badValue), c.end());

list:c.remove_if(badValue)

(2)对于标准关联容器,解决方案有两种:

第一种:利用remove_copy_if把我们需要复制的值复制到一个新容器中,然后把原来容器的内容和新容器的内容相互交换:

1
2
3
4
5
6
7
8
9
10
11
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的成员函数。

第二种:可以写一个循环,在遍历过程中删除元素。

一个直截了当的方法:

1
2
3
4
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); ++i )

if(badValue(*i))
c.erase(i);

但是这样会导致不确定的行为,当容器中的一个元素被删除时,指向该元素的所有迭代器都变得无效,c.erase(i)之后,i变为无效值。

为了避免这个问题,要确保在调用erase之前,有一个迭代器指向c中的下一个元素。

最简单的方法是,对i使用后缀++

1
2
3
4
5
for(AssocContainer<int>::iterator i =c.begin(); i != c.end(); (什么都不做) )
if(badValue(*i))
c.erase(i++); //这样在执行erase之前已经对i递增为新的值,旧的值删除变为了无效值,但i已经成为新值
else
++i;

问题3:如果我们不仅要删除使badValue返回true的元素,还当元素被删除时向日志文件记录一条信息

1
2
3
4
5
6
7
8
9
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完成,它是指向紧随被删除元素的下一个元素的有效迭代器。

1
2
3
4
5
6
7
8
for(SeqContainer<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相同的方式。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!