[英]Difference between std::remove and erase for vector?
我怀疑我想澄清一下。 我知道erase
和std::remove
之间std::vector
的不同行为,其中第一个物理上从向量中删除元素,从而减小大小,而另一个仅移动元素而使容量不变。
这仅仅是出于效率原因吗? 通过使用erase
, std::vector
中的所有元素将移位1,从而导致大量复制; std::remove
只是执行“逻辑”删除操作,并且通过四处移动来使向量保持不变。 如果物体很重,这种差异可能很重要,对不对?
这仅仅是出于效率原因吗? 通过使用擦除,std :: vector中的所有元素都将移位1,从而导致大量复制; std :: remove只是执行“逻辑”删除操作,并且通过四处移动来使向量保持不变。 如果物体很重,差异会很重要,对不对?
使用这个习惯用法的原因正是这样。 性能上有好处,但单次擦除则无济于事。 重要的是您是否需要从向量中删除多个元素。 在这种情况下, std::remove
将仅将每个未删除的元素复制一次到其最终位置,而vector::erase
方法会将所有元素从该位置多次移动到末尾。 考虑:
std::vector<int> v{ 1, 2, 3, 4, 5 };
// remove all elements < 5
如果逐个遍历矢量删除元素,则会删除1,导致其余元素的副本移位(4)。 然后,您将删除2并将所有剩余元素移动一(3)...如果看到模式,则这是O(N^2)
算法。
在std::remove
的情况下,该算法将维护读写头,并在容器上进行迭代。 对于前四个元素,将移动读取头并测试该元素,但不复制任何元素。 仅对于第五个元素,该对象将从最后一个位置复制到第一个位置,并且算法将完成一个副本并将迭代器返回到第二个位置。 这是一个O(N)
算法。 后面带有范围的std::vector::erase
将导致破坏所有其余元素并调整容器的大小。
正如其他人提到的那样,在标准库中,算法被应用于迭代器,并且缺乏对要迭代的序列的了解。 这种设计比其他算法可以识别容器的方法更加灵活,因为该算法的单个实现可以与符合迭代器要求的任何序列一起使用。 例如,考虑一下std::remove_copy_if
,即使使用无容器,也可以使用生成/接受序列的迭代器来使用它:
std::remove_copy_if(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(),
std::ostream_iterator<int>(std::cout, " "),
[](int x) { return !(x%2); } // is even
);
单行代码将过滤掉标准输入中的所有偶数,并将其转储到标准输出,而无需将所有数字加载到容器的内存中。 这是拆分的优点,缺点是算法无法修改容器本身,只能修改迭代器引用的值。
std::remove
是STL中的一种算法,与容器无关。 它需要一些概念,这是正确的,但已设计为还可以使用大小固定的C数组。
std::remove
简单地返回一个新的end()
迭代器,以指向最后一个未删除的元素之后的元素(从返回值到end()
的项目数将与要删除的项目数匹配,但是没有确保它们的值与您要删除的值相同-它们处于有效但未指定的状态)。 这样做是为了使其可以用于多种容器类型(基本上是ForwardIterator
可以迭代通过的任何容器类型)。
调整大小后, std::vector::erase
实际上会设置新的end()
迭代器。 这是因为vector
的方法实际上知道如何调整其迭代器(可以使用std::list::erase
, std::deque::erase
等来完成此操作)。
remove
组织给定的容器以删除不需要的对象。 容器的擦除功能实际上处理容器需要完成的“删除”操作。 这就是为什么它们是分开的。
我认为这与需要直接访问向量本身有关,以便能够调整其大小。 std :: remove仅具有访问迭代器的权限,因此它无法告诉向量“嘿,您现在的元素更少了”。
请参阅yves Baumes关于为何以这种方式设计std :: remove的答案。
是的,这就是要点。 请注意,其他erase
性能也有所不同的其他标准容器也支持erase
(例如list :: erase为O(1)),而std::remove
则与容器无关,并且可以与任何类型的正向迭代器一起使用 (因此它也适用于裸阵列)。
的种类。 诸如remove的算法在迭代器(代表集合中元素的抽象)上工作,这些迭代器不一定知道它们在操作哪种集合类型,因此无法调用集合中的成员进行实际的删除。
这很好,因为它允许算法在任何容器上以及在整个集合的子集范围内通用地工作。
而且,正如您所说,为了提高性能,如果您需要的只是访问逻辑端点位置并传递给其他算法,则实际上不必删除(并销毁)元素。
标准库算法对序列进行操作。 一个序列是由一对迭代器定义的。 第一个指向序列中的第一个元素,第二个指向序列中的最后一个。 就这样; 算法不在乎序列的来源。
标准库容器保存数据值,并提供一对迭代器,这些迭代器指定算法使用的序列。 它们还提供成员函数,这些成员函数可以利用容器的内部数据结构来更有效地执行与算法相同的操作。
尝试以下代码以获得更好的理解。
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
const auto newend (remove(begin(v), end(v), 2));
for(auto a : v){
cout << a << " ";
}
cout << endl;
v.erase(newend, end(v));
for(auto a : v){
cout << a << " ";
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.