stl 的remove 记我很费解

wansuiwht 2009-11-18 09:19:38
这个remove让我很费解啊。
根据介绍它只是把后面的元素覆盖到要删除的元素的位置。
我也在网上查了一下这个函数,内部实现的原理挺别扭的。
stl设计这个函数是为什么呢。是为了性能吗。
...全文
342 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
深圳大哥 2012-03-15
  • 打赏
  • 举报
回复
remove很费解哈
grpubr 2009-11-19
  • 打赏
  • 举报
回复
1 2 3 3 4 5
remove(beg,end,1)就是
2 3 3 4 5 5
remove(beg,end,3)就是
1 2 4 5 4 5
remove(beg,end,5)还是
1 2 3 3 4 5
xuhb95083023 2009-11-19
  • 打赏
  • 举报
回复
对,怪异的实现方式就是为了性能
taodm 2009-11-19
  • 打赏
  • 举报
回复
摆显贴?
zucc_bug 2009-11-19
  • 打赏
  • 举报
回复
楼主的头像让我很费解,她躺在那里干嘛呢?
lovesi3344 2009-11-19
  • 打赏
  • 举报
回复
11楼,你的问题很尖锐,很有才
哈哈哈哈
笑死我了

[Quote=引用 11 楼 zucc_bug 的回复:]
楼主的头像让我很费解,她躺在那里干嘛呢?
[/Quote]
cphj 2009-11-19
  • 打赏
  • 举报
回复
为了性能

也为了算法的原子性和正交性
zdeepblue 2009-11-19
  • 打赏
  • 举报
回复
除了性能,主要是STL的算法和容器的分离。算法只知道迭代器类型,不知道如何真正删除(erase)内容。只有容器才知道怎么删。那么,remove算法只能进行数据移动,然后让容器去做真正的删除工作
wansuiwht 2009-11-18
  • 打赏
  • 举报
回复
当时考虑不周啊
Fleeboy 2009-11-18
  • 打赏
  • 举报
回复
我怎么觉得那个remove实现很自然呢
wansuiwht 2009-11-18
  • 打赏
  • 举报
回复
在学这个算法之前,我自己实现了一下算法,现在看来, 我那个算法有点问题,只是简单的用后面的值覆盖,
现在看来,如果两个要删除的元素是挨着的话,我那算法就有问题了。等于,只覆盖了后面那个值。
mstlq 2009-11-18
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 akirya 的回复:]
假如让你设计这个算法你咋实现?
[/Quote]

up……
请楼主多从此角度出发看问题,很多东西都能迎刃而解……
  • 打赏
  • 举报
回复
假如让你设计这个算法你咋实现?
heguodong 2009-11-18
  • 打赏
  • 举报
回复
你看看STL容器的内存分配机制
1,对于小对象的分配和大对象的分配是不一样的
超过128字节的直接调用::new在全局内存分配
否则在自己保留堆了分配
2,小对象的分配
a,在需要分配小对象内存的时候,直接::new一大块内存保留起来
b,在这快保留内存里面分配小对象的内存,如果后面还要继续分配小对象,就接在前面的小对象后面继续分配
c,这快保留内存分配完以后的状态是,对象挨得很紧揍,中间没有间隔的空于内存(也叫内存碎片)
d,remove的时候,如果你只是简单地将要romove的对象摸除,那么这里就形成了一个碎片,如果是把后面的往前移过来,那么中间就不存在碎片,而是在最后面腾出了空余内存,以后分配小对象的时候可以继续接在后面.
mmilmf 2009-11-18
  • 打赏
  • 举报
回复
:)
lovesi3344 2009-11-18
  • 打赏
  • 举报
回复

很不错的文章
给分吧
wansuiwht 2009-11-18
  • 打赏
  • 举报
回复
我将从remove的复习开始这个条款,因为remove是STL中最糊涂的算法。误解remove很容易,驱散所有关于remove行为的疑虑——为什么它这么做,它是怎么做的——是很重要的。

这是remove的声明:

template<class ForwardIterator, class T>
ForwardIterator remove(ForwardIterator first, ForwardIterator last,
const T& value);
就像所有算法,remove接收指定它操作的元素区间的一对迭代器。它不接收一个容器,所以remove不知道它作用于哪个容器。此外,remove也不可能发现容器,因为没有办法从一个迭代器获取对应于它的容器。

想想怎么从容器中除去一个元素。唯一的方法是调用那个容器的一个成员函数,几乎都是erase的某个形式,(list有几个除去元素的成员函数不叫erase,但它们仍然是成员函数。)因为唯一从容器中除去一个元素的方法是在那个容器上调用一个成员函数,而且因为remove无法知道它正在操作的容器,所以remove不可能从一个容器中除去元素。这解释了另一个令人沮丧的观点——从一个容器中remove元素不会改变容器中元素的个数:

vector<int> v; // 建立一个vector<int> 用1-10填充它
v.reserve(10); // (调用reserve的解释在条款14)
for (int i = 1; i <= 10; ++i) {
v.push_back(i);
}
cout << v.size(); // 打印10
v[3] = v[5] = v[9] = 99; // 设置3个元素为99
remove(v.begin(), v.end(), 99); // 删除所有等于99的元素
cout << v.size(); // 仍然是10!要搞清这个例子的意思,记住下面这句话:

remove并不“真的”删除东西,因为它做不到。

重复对你有好处:

remove并不“真的”删除东西,因为它做不到。

remove不知道它要从哪个容器删除东西,而没有容器,它就没有办法调用成员函数,而如果“真的”要删除东西,那就是必要的。

上面解释了remove不做什么,而且解释了为什么它不做。我们现在需要复习的是remove做了什么。

非常简要地说一下,remove移动指定区间中的元素直到所有“不删除的”元素在区间的开头(相对位置和原来它们的一样)。它返回一个指向最后一个的下一个“不删除的”元素的迭代器。返回值是区间的“新逻辑终点”。

举个例子,这是v在调用remove前看起来的样子:



如果我们把remove的返回值存放在一个叫做newEnd的新迭代器中:

vector<int>::iterator newEnd(remove(v.begin(), v.end(), 99));这是调用后v看起来的样子:



这里我用问号来标明那些在概念上已经从v中被删除,但继续存在的元素的值。

如果“不删除的”元素在v中的v.begin()和newEnd之间,“删除的”元素就必须在newEnd和v.end()之间——这好像很合理。事实上不是这样!“删除的”值完全不必再存在于v中了。remove并没有改变区间中元素的顺序,所以不会把所有“删除的”元素放在结尾,并安排所有“不删除的”值在开头。虽然标准没有要求,但一般来说区间中在新逻辑终点以后的元素仍保持它们的原值。调用完remove后,在我知道的所有实现中,v看起来像这样:



正如你所见,两个曾经存在于v的“99”不再在那儿了,而一个“99”仍然存在。一般来说,调用完remove后,从区间中删除的值可能是也可能不在区间中继续存在。大多数人觉得这很奇怪,但为什么?你要求remove除去一些值,所以它做了。你并没有要求它把删除的值放在一个你以后可以获取的特定位置,所以它没有做。有问题吗?(如果你不想失去任何值,你可能应该调用partition或stable_partition而不是remove,partition在条款31中描述。)

remove的行为听起来很可恶,但它只不过是算法操作的附带结果。在内部,remove遍历这个区间,把要“删除的”值覆盖为后面要保留的值。这个覆盖通过对持有被覆盖的值的元素赋值来完成。

你可以想象remove完成了一种压缩,被删除的值表演了在压缩中被填充的洞的角色。对于我们的vector v,它按照下面的表演:

remove检测v[0],发现它的值不是要被删除的,然后移动到v[1]。同样的情况发生在v[1]和v[2]。
发现v[3]应该被删除,所以它记录下v[3]的值应该被覆盖,然后它移动到v[4]。这类似记录v[3]是一个需要填充的“洞”。
发现v[4]的值应该被保持,所以它把v[4]赋给v[3],记录下v[4]应该被覆盖,然后移动到v[5]。继续类似的压缩,它用v[4]“填充”v[3]而且记录v[4]现在是一个洞。
发现v[5]应该被删除,所以忽略并它移动到v[6]。仍然记得v[4]是一个等待填充的洞。
发现v[6]是一个应该保留的值,所以把v[6]赋给v[4]。记得v[5]现在是下一个要被填充的洞,然后移到v[7]。
在某种意义上类似上面的,检查v[7]、v[8]和v[9]。把v[7]赋给v[5],v[8]赋给v[6],忽略v[9],因为v[9]的值是要被删除的。
返回指定下一个要被覆盖的元素的迭代器,在这个例子中这个元素是v[7]。

正如条款33所解释的,事实上当remove在删除时覆盖的值是指针时,会有重要的影响。但是对于本条款,知道remove不从容器中除去任何元素因为它做不到就够了。只有容器成员函数可以除去容器元素,而那是本条款的整个要点:如果你真的要删除东西的话,你应该在remove后面接上erase。
你要erase的元素很容易识别。它们是从区间的“新逻辑终点”开始持续到区间真的终点的原来区间的元素。要除去那些元素,你要做的所有事情就是用那两个迭代器调用erase的区间形式(参见条款5)。因为remove本身很方便地返回了区间新逻辑终点的迭代器,这个调用很直截了当:

vector<int> v; // 正如从前
v.erase(remove(v.begin(), v.end(), 99), v.end()); // 真的删除所有
// 等于99的元素
cout << v.size(); // 现在返回7
把remove的返回值作为erase区间形式第一个参数传递很常见,这是个惯用法。事实上,remove和erase是亲密联盟,这两个整合到list成员函数remove中。这是STL中唯一名叫remove又能从容器中除去元素的函数:

list<int> li; // 建立一个list
// 放一些值进去
li.remove(99); // 除去所有等于99的元素:
// 真的删除元素,
// 所以它的大小可能改变了
坦白地说,调用这个remove函数是一个STL中的矛盾。在关联容器中类似的函数叫erase,list的remove也可以叫做erase。但它没有,所以我们都必须习惯它。我们所处于的世界不是所有可能中最好的世界,但却是我们所处的。(附加一点,条款44指出,对于list,调用remove成员函数比应用erase-remove惯用法更高效。)

一旦你知道了remove不能“真的”从一个容器中删除东西,和erase联合使用就变成理所当然了。你要记住的唯一其他的东西是remove不是唯一这种情况的算法。另外有两种“类似remove”的算法:remove_if和unique。

remove和remove_if之间的相似性很直截了当。所以我不会细讲,但unique行为也像remove。它用来从一个区间删除东西(邻近的重复值)而不用访问持有区间元素的容器。结果,如果你真的要从容器中删除元素,你也必须成对调用unique和erase,unique在list中也类似于remove。正像list::remove真的删除东西(而且比erase-remove惯用法高效得多)。list::unique也真的删除邻近的重复值(也比erase-unique高效)。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/vbanglev/archive/2007/02/22/1512521.aspx
lovesi3344 2009-11-18
  • 打赏
  • 举报
回复

沙发
楼主,20分全给我吧

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧