Effective STL 第一章 第9条

9 篇文章 0 订阅

第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相同的方式。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值