看到《Effective STL》条款 9 的时候想到了我以前复习的“如何正确使用迭代器删除元素”,我面试时使用的也是里面的方法,看面试官的反应好像也没有什么问题,还问了我一些我早已整理过的考点。但看到条款 9 之后,我就觉得自己以前回答得没什么水平了。

文本参考了条款 9 和条款 32。

remove()

<algorithm> 中的 remove() 的声明为:

template<class ForwardIt, class T>
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);

也就是从范围 [first, last) 内移除所有满足特定判别标准的元素,并返回新结尾的尾后迭代器。需要注意的是,remove() 并不真正删除东西,因为它做不到remove() 移动指定区间中的所有元素直到所有“不删除的”元素在区间的开头(相对位置和原来的一样)。它返回一个指向最后一个“不删除的”元素的下一个元素的迭代器。返回值是区间的“新逻辑终点”。

vector<int> v;
v.reserver(10);
for(int i = 1; i <= 10; ++i)
{
v.push_back(i);
} v[3] = v[5] = v[9] = 99;
vecitor<int>::iterator newEnd(remove(v.begin(), v.end(), 99));

调用 remove() 之前:

调用 remove() 之后:

erase-remove 惯用法

如果要真正删除东西的话,应该在 remove() 后面接上 erase()。要 erase() 的元素很容易识别:它们是从区间的“新逻辑终点”开始持续到区间真正重点的原来区间的元素。要除去那些元素,要做的事情就是用两个迭代器调用 erase() 的区间形式。

vector<int> v;
v.erase(remove(v.begin(), v.end(), 99), v.end());

remove 的返回值作为 erase() 区间形式第一个实参传递很常见,这是个惯用法。

remove_if() 和 remove_copy_if()

序列容器

如果要删除的不是每个有特定值的物体,而是消除下面的判断式

bool badValue(int x); // 返回 x 是否是 "bad".

对于序列容器,要做的只是把每个 remove() 替换成 remove_if(),然后就完成了:

c.erase(remove_if(c.begin(), c.end(), badValue), c.end());

这样面试题“从 vector 中删除 3 的倍数”就变得更简单了:

bool ThreeTimes(int num)
{
return num % 3 == 0 ? true : false;
} int main()
{
// ...
// 一行代码, 没有循环.
vec.erase(std::remove_if(vec.begin(), vec.end(), ThreeTimes), vec.end());
// ...
}

真是好笑,听到面试官问我这道题,我心中窃喜,还想着秀一秀我关于“如何正确使用迭代器删除元素”的清晰思路,哈哈,真是贻笑大方。

更完美的版本:

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std; int main()
{
vector<int> vec{19, 21, 28, 13, 16, 2, 3, 29, 12,
4, 25, 17, 26, 5, 20, 18, 30, 6, 22,
11, 24, 14, 7, 23, 1, 15, 27, 8, 9, 10}; // 使用 lambda 函数作为谓词. 当然 -> bool 可以省略.
vec.erase(
remove_if(vec.begin(),
vec.end(),
[&](int num) -> bool { return num % 3 == 0; }),
vec.end()); cout << "after erase-remove\n";
cout << "size: " << vec.size() << "\n";
cout << "capacity: " << vec.capacity() << "\n\n"; // 使用交换技巧收缩空间.
vector<int>(vec).swap(vec); cout << "after swap trick\n";
cout << "size: " << vec.size() << "\n";
cout << "capacity: " << vec.capacity() << "\n"; return 0;
}

输出如下:

after erase-remove
size: 20
capacity: 30 after swap trick
size: 20
capacity: 20

关联容器

对于关联容器,remove_if() 不是很直截了当。有两种方法处理该问题:

  1. remove_copy_if(),更容易,效率较低;
  2. 循环,更高效。

关于 remove_copy_if(),它把需要的值拷贝到另一个新容器中,然后把原容器的内容和新的交换:

#include <algorithm>
#include <set> bool ThreeTimes(int num)
{
return num % 3 == 0 ? true : false;
} int main()
{
std::set<int> c{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::set<int> notThreeTimes; // 存储非 3 的倍数的临时容器. // 从 c 复制不删除的值到 notThreeTimes.
std::remove_copy_if(c.begin(), c.end(),
std::inserter(notThreeTimes, notThreeTimes.end()),
ThreeTimes); c.swap(notThreeTimes); // 交换 c 和 notThreeTimes. return 0;
}

这种方法的缺点是它复制了所有不删除的元素,而这样的复制开销花费很大。

for 循环很简单,注意 erase() 时处理好迭代器就行:

for(std::set<int>::iterator i = c.begin(); i != c.end();)
{
if(ThreeTimes(*i))
{
// 还可以向日志文件中写入一条消息.
// logFile << "Erasing " << *i << '\n';
c.erase(i++);
}
else
{
++i;
}
}

当然,要是序列容器也需要在 erase() 时有其它操作,同样可以使用 for 循环,只需注意序列式容器使用迭代器删除元素的技巧就行。

结论

去除一个容器中特定值的所有对象:

  1. 如果容器是 vectorstirngdeque,使用 erase-remove 惯用法;
  2. 如果容器是 list,使用 list::remove
  3. 如果容器是标准关联容器,使用它的 erase() 成员函数。

去除一个容器中满足一个特定判定式的所有对象:

  1. 如果容器是 vectorstringdeque,使用 erase-remove_if 惯用法;
  2. 如果容器是 list,使用 list::romove_if()
  3. 如果容器是标准关联容器,使用 remove_copy_if()swap(),或写一个循环来遍历容器元素,但把迭代器传给 erase() 时记得后置递增它。

在循环内做某些事情(除了删除对象之外):

  1. 如果容器是标准序列容器,写一个循环来遍历元素,每当调用 erase() 时记得用它的返回值更新迭代器;
  2. 如果容器是标准关联容器,写一个循环来遍历元素,当把迭代器传给 erase() 时记得后置递增它。

Erase-Remove 惯用法的更多相关文章

  1. STL笔记(4)关于erase,remove

    STL笔记(4)关于erase,remove 你要erase的元素很容易识别.它们是从区间的“新逻辑终点”开始持续到区间真的终点的原来区间的元素.要除去那些元素,你要做的所有事情就是用那两个迭代器调用 ...

  2. Jquery remove 高级用法

    Jquery remove 高级用法 html 代码 <div class="file-image">abc1111</div><div class= ...

  3. vector中erase()与insert()用法

    erase()用法:https://blog.csdn.net/duan19920101/article/details/50717748 注:erase是删除指定位置的元素,不能删除给定元素值.若要 ...

  4. RAII惯用法:C++资源管理的利器(转)

    RAII惯用法:C++资源管理的利器 RAII是指C++语言中的一个惯用法(idiom),它是“Resource Acquisition Is Initialization”的首字母缩写.中文可将其翻 ...

  5. 做个地道的c++程序猿:copy and swap惯用法

    如果你对外语感兴趣,那肯定听过"idiom"这个词.牛津词典对于它的解释叫惯用语,再精简一些可以叫"成语".想要掌握一门语言,其中的"成语" ...

  6. c++中string.erase()函数的用法(转)

    erase函数的原型如下:(1)string& erase ( size_t pos = 0, size_t n = npos );(2)iterator erase ( iterator p ...

  7. map中的erase成员函数用法

    转载于 http://www.cnblogs.com/graphics/archive/2010/07/05/1771110.html  http://hi.baidu.com/sdkinger/it ...

  8. _.remove的用法

    var array = [1, 2, 3, 4]; var evens = _.remove(array, function(n) { return n % 2 == 0; }); console.l ...

  9. vector erase的错误用法

    直接写 a.erase(it)是错误的,一定要写成it=a.erase(it)这个错误编译器不会报错.而且循环遍历删除的时候,删除了一个元素,容器里会自动向前移动,删除一个元素要紧接着it--来保持位 ...

随机推荐

  1. 1.3RDD的设计与运行原理

    此文为个人学习笔记如需系统学习请访问http://dblab.xmu.edu.cn/blog/1709-2/ 提供一种通用的数据抽象 RDD典型的执行过程如下: RDD读入外部数据源(或者内存中的集合 ...

  2. springgateway

    SpringGateAway: 先进行鉴权,然后进行路由,日志什么等等

  3. 理解ASP.NET Core - [02] Middleware

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用 ...

  4. 从零开始实现简单 RPC 框架 6:网络通信之 Netty

    网络通信的开发,就涉及到一些开发框架:Java NIO.Netty.Mina 等等. 理论上来说,类似于序列化器,可以为其定义一套统一的接口,让不同类型的框架实现,事实上,Dubbo 就是这么干的. ...

  5. NCNN优化实时面部关键点检测

    效果图 演示手机为红米10X pro,可以实时跑人脸检测+关键点识别二个模型. 主要优化 上次看见有人讨论人脸检测与关键点识别,用的是opencv相关,于是想看下深度神经网络相关部分的进展,先选定了推 ...

  6. MySQL——MySQL体系结构

    1.连接层 2.SQL层: (1)将接收到的SQL语句,解析成执行计划 (2)查询优化器 ---->选择最优的执行计划,交给存储引擎 (3)查询缓存: 缓存执行计划 (4)附加功能:权限. 语法 ...

  7. Docker(42)- 镜像原理之联合文件系统

    前言 学习狂神老师的 Docker 系列课程,并总结 镜像是什么 镜像是一种轻量级.可执行的独立软件保,用来打包软件运行环境和基于运行环境开发的软件 他包含运行某个软件所需的所有内容,包括代码.运行时 ...

  8. RabbitMQ-如何保证消息在99.99%的情况下不丢失

    1. 简介 MQ虽然帮我们解决了很多问题,但是也带来了很多问题,其中最麻烦的就是,如何保证消息的可靠性传输. 我们在聊如何保证消息的可靠性传输之前,先考虑下哪些情况下会出现消息丢失的情况. 首先,上图 ...

  9. Tomcat配置支持war包部署

    Tomcat配置支持war包部署 #cat /data/tomcat/conf/server.xml <?xml version='1.0' encoding='utf-8'?> < ...

  10. PHP的bz2压缩扩展工具

    在日常的开发和电脑使用中,我们经常会接触到压缩和解压的一些工具,PHP 也为我们准备了很多相关的操作扩展包,都有直接可用的函数能够方便的操作一些压缩解压功能.今天,我们先学习一个比较简单但不太常用的压 ...