Erase-Remove 惯用法
看到《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()
不是很直截了当。有两种方法处理该问题:
remove_copy_if()
,更容易,效率较低;- 循环,更高效。
关于 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
循环,只需注意序列式容器使用迭代器删除元素的技巧就行。
结论
去除一个容器中特定值的所有对象:
- 如果容器是
vector
、stirng
或deque
,使用 erase-remove 惯用法; - 如果容器是
list
,使用list::remove
; - 如果容器是标准关联容器,使用它的
erase()
成员函数。
去除一个容器中满足一个特定判定式的所有对象:
- 如果容器是
vector
、string
或deque
,使用 erase-remove_if 惯用法; - 如果容器是
list
,使用list::romove_if()
; - 如果容器是标准关联容器,使用
remove_copy_if()
和swap()
,或写一个循环来遍历容器元素,但把迭代器传给erase()
时记得后置递增它。
在循环内做某些事情(除了删除对象之外):
- 如果容器是标准序列容器,写一个循环来遍历元素,每当调用
erase()
时记得用它的返回值更新迭代器; - 如果容器是标准关联容器,写一个循环来遍历元素,当把迭代器传给
erase()
时记得后置递增它。
Erase-Remove 惯用法的更多相关文章
- STL笔记(4)关于erase,remove
STL笔记(4)关于erase,remove 你要erase的元素很容易识别.它们是从区间的“新逻辑终点”开始持续到区间真的终点的原来区间的元素.要除去那些元素,你要做的所有事情就是用那两个迭代器调用 ...
- Jquery remove 高级用法
Jquery remove 高级用法 html 代码 <div class="file-image">abc1111</div><div class= ...
- vector中erase()与insert()用法
erase()用法:https://blog.csdn.net/duan19920101/article/details/50717748 注:erase是删除指定位置的元素,不能删除给定元素值.若要 ...
- RAII惯用法:C++资源管理的利器(转)
RAII惯用法:C++资源管理的利器 RAII是指C++语言中的一个惯用法(idiom),它是“Resource Acquisition Is Initialization”的首字母缩写.中文可将其翻 ...
- 做个地道的c++程序猿:copy and swap惯用法
如果你对外语感兴趣,那肯定听过"idiom"这个词.牛津词典对于它的解释叫惯用语,再精简一些可以叫"成语".想要掌握一门语言,其中的"成语" ...
- c++中string.erase()函数的用法(转)
erase函数的原型如下:(1)string& erase ( size_t pos = 0, size_t n = npos );(2)iterator erase ( iterator p ...
- map中的erase成员函数用法
转载于 http://www.cnblogs.com/graphics/archive/2010/07/05/1771110.html http://hi.baidu.com/sdkinger/it ...
- _.remove的用法
var array = [1, 2, 3, 4]; var evens = _.remove(array, function(n) { return n % 2 == 0; }); console.l ...
- vector erase的错误用法
直接写 a.erase(it)是错误的,一定要写成it=a.erase(it)这个错误编译器不会报错.而且循环遍历删除的时候,删除了一个元素,容器里会自动向前移动,删除一个元素要紧接着it--来保持位 ...
随机推荐
- 深入浅出Mybatis系列(八)---objectFactory、plugins、mappers
1.objectFactory是干什么的? 需要配置吗? MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成.默认的对象工厂需要做的仅仅是实例化 ...
- Object--Date--calendar--System--StringBuilder--基本数据类型包装类型
Object java.lang.Object类是Java语言中的根类,即所有类的父类 默认toString()方法打印的是对象在堆中的地址值 默认equals()方法比较的也是地址(String中对 ...
- Failed to start LSB: Bring up/down错误解决方法
很多朋友在使用centos7系统时,有时候需要分配多个IP地址,这就涉及到修改网卡配置,但是在修改完网卡配置时,重启网络服务时会出现"Failed to start LSB: Bring u ...
- const 修饰
int * const grape_jelly; 指针是只读的. const int * grape; int const * grape; 指针所指向的对象是只读的. 对象和指针有可能都是只读的: ...
- java的stream的使用
过滤 filter: //匹配第一个元素 Optional<Integer> findFirst=list.stream().filter(x->x>6).findFirst( ...
- Linux常用命令(一)之文件处理命令
分时的多用户.多任务的操作系统 多数的网络协议的支持(unix和tcp/ip协议是同时发展起来的),方便的远程管理(可以通过图形.命令行) 强大的内存管理和文件管理系统 大量的可用软件和免费软件(游戏 ...
- idea无法使用中文输入法输入
问题--idea无法使用中文输入 原因:idea本身版本过高,所以需要你强制减低它的jdk版本 解决:使用配置idea环境变量解决 ps:目前适用于任何版本的jdk和idea 步骤: 1.新建一个ID ...
- (一)Superset 1.3图表篇——Table
本系列文章基于Superset 1.3.0版本.1.3.0版本目前支持分布,趋势,地理等等类型共59张图表.本次1.3版本的更新图表有了一些新的变化,而之前也一直没有做过非常细致的图表教程. 而且目前 ...
- Docker之Alpine制作jre镜像(瘦身)+自定义镜像上传阿里云
alpine制作jdk镜像 alpine Linux简介 1.Alpine Linux是一个轻型Linux发行版,它不同于通常的Linux发行版,Alpine采用了musl libc 和 BusyBo ...
- vue的常见理论问题
1.什么是 mvvm? mvvm 和 mvc 区别? MVVM 是 Model-View-ViewModel 的缩写.mvvm 是一种设计思想.Model 层代表数据模型,View 代表 UI 组件. ...