C++ 矢量擦除推进迭代器

c++ vector erase advances iterator

本文关键字:迭代器 擦除 C++      更新时间:2023-10-16

以下简化代码有效,因为它删除了所有向量元素。但是,我不明白为什么。由于f.erase(r)没有捕获返回值(这将是新的迭代器值),并且没有其他迭代器增量器,并且根据文档,erase(iterator position)参数不是通过引用传递的,因此迭代器在哪里前进?

#include <iostream>
#include <vector>
int main ()
{
std::vector<int> f = {1,2,3,4,5};
auto r = f.begin();
while (r != f.end())
{
std::cout << "Erasing " << *r << std::endl;
f.erase(r);
}
return 0;
}

迭代器在哪里高级?

它没有,迭代器一直指向同一位置。 这在技术上是未定义的行为,但如果你考虑循环实际在做什么,你就会明白为什么你会得到"正确"的结果。

矢量包含指向它存储的对象的指针。 您的迭代器将指向该内存,并偏移到您想要的元素。 在这种情况下,它将指向数据的开头。 擦除第一个元素时,迭代器将失效,但它仍指向向量的开头。erase向前移动所有元素,因此当您进入下一次迭代时,您的状态与第一次迭代中的状态相同,只是向量小了一个元素。 您反复执行此操作,直到没有剩余的元素并end() == begin()

不过,您不应该依赖这种情况总是发生,而应该使用clear()从向量中删除所有元素。

迭代器在哪里高级?

迭代器不会高级。你的代码似乎工作的事实只是偶然的,事实上你有未定义的行为。

从 cpp首选项 上std::vector::erase

使擦除点处或之后的迭代器和引用失效,包括 end() 迭代器。

调用f.erase(r);后,不允许使用r。如果你这样做,有趣的事情就会发生。

你必须写

while (r != f.end())
{
std::cout << "Erasing " << *r << std::endl;
r = f.erase(r);
^^^^^^^^^^^^^^^^
}

因为擦除后迭代器变得无效。

或者你可以写

f.clear();

因为循环会删除向量的所有元素。

考虑到由于迭代器 r 仅在循环中使用,因此最好在使用它的循环范围内声明它。

例如
for ( auto r = f.begin(); r != f.end(); )
{
std::cout << "Erasing " << *r << std::endl;
r = f.erase(r);
}
根据

文档,

你应该读到最后:

向量::擦除 - C++ 参考

由于矢量使用数组作为其基础存储,因此擦除矢量端以外的位置的元素会导致容器在段擦除到新位置后重新定位所有元素。与其他类型的序列容器(如列表或forward_list)对同一操作执行的操作相比,这通常是低效的操作。

返回值

一个迭代器,指向被函数调用擦除的最后一个元素之后的元素的新位置。如果操作擦除了序列中的最后一个元素,则这是容器结束。

向量::擦除 - C++ 参考

最后:

迭代器有效性

指向位置(或第一个)及以后的迭代器、指针和引用无效,所有迭代器、指针和对位置(或第一个)之前元素的引用都保证继续引用它们在调用之前引用的相同元素。

在大多数情况下,在向量中添加/删除元素(包括 erase())会使引用和迭代器无效。使用旧的迭代器会导致未定义的行为。

正如@Nathan评论中提到的,f.clear()就是您所需要的。

将迭代器传递给erase会使该迭代器失效,因此进一步使用它(在下一次迭代中将其传递给erase时)具有未定义的行为。因此,该程序实际上并不"工作"。它似乎有效,因为这是一种可能的行为。但这种行为并不能得到保证。

erase

会使r无效。在此之后取消引用r会导致未定义的行为。


但在现实世界中,除非实现有意检查迭代器的有效性,否则它不应该引起任何问题。

vector迭代器通常只存储指向元素的指针。删除元素时,其右侧的所有元素都会向左移动一个位置。因此,曾经被删除的元素占用的内存位置将被下一个元素占用。