我有一个疑问,我想在脑海中澄清一下。我知道在第一个从向量中物理删除一个元素,减小大小,而另一个只是移动一个元素而使容量保持不变的位置std::vector
之间erase
和位置的不同行为。std::remove
这仅仅是出于效率原因吗?使用erase
时,a 中的所有元素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
维护一个读写头,并迭代容器。对于前 4 个元素,将移动读取头并测试元素,但不会复制任何元素。仅对于第五个元素,对象将从最后一个位置复制到第一个位置,并且算法将以单个副本完成并将迭代器返回到第二个位置。这是一个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
其他标准容器也支持它的性能特征不同(例如list::erase是 O(1)),而std::remove
它与容器无关并且适用于任何类型的前向迭代器(因此它适用于例如裸数组以及)。
有点儿。诸如 remove 之类的算法在迭代器(这是表示集合中元素的抽象)上工作,它们不一定知道它们正在操作哪种类型的集合 - 因此不能调用集合上的成员来进行实际的删除。
这很好,因为它允许算法在任何容器以及作为整个集合子集的范围上通用。
此外,正如您所说,为了性能 - 如果您只需要访问逻辑结束位置以传递给另一个算法,则可能不需要实际删除(和销毁)元素。
标准库算法对序列进行操作。序列由一对迭代器定义;第一个指向序列中的第一个元素,第二个指向序列的末尾。就这样; 算法不关心序列来自哪里。
标准库容器保存数据值,并提供一对迭代器,指定算法使用的序列。它们还提供成员函数,通过利用容器的内部数据结构,可以更有效地执行与算法相同的操作。