向量删除迭代器

84

我有以下这段代码:

int main()
{
    vector<int> res;
    res.push_back(1);
    vector<int>::iterator it = res.begin();
    for( ; it != res.end(); it++)
    {
        it = res.erase(it);
        //if(it == res.end())
        //  return 0;
    }
}

通过函数调用擦除最后一个元素,返回指向紧随已删除元素之后的新位置的随机访问迭代器。如果操作删除序列中的最后一个元素,则返回指向 vector 尾部的迭代器。

这段代码会崩溃,但是如果使用if(it == res.end())语句并返回,它就可以工作了。为什么会这样?for循环是否缓存了res.end(),导致不等运算符失败?


类似问题:https://dev59.com/-HRC5IYBdhLWcg3wSu97 - Naveen
7
因为这只是代码的简化,我并不打算删除实际代码中的所有元素。 - hidayat
10个回答

168

res.erase(it) 每次都会返回下一个有效的迭代器,如果你删除了最后一个元素,它将指向 .end()

在循环结束时,总是调用 ++it ,所以你会增加不允许增加的 .end()

但仅仅检查 .end() 还存在一个 bug,因为你每次迭代都会跳过一个元素(it 先被 .erase() 的返回值“增加”,然后再被循环“增加”一次)

你可能需要像这样做:

 while (it != res.end()) {
        it = res.erase(it);    
 }

删除每个元素

(为了完整起见:我假设这是一个简化的例子,如果您只想让所有元素消失而无需对其执行操作(例如删除),则应该调用res.clear()

当您仅有条件地擦除元素时,您可能需要类似以下的内容:

for ( ; it != res.end(); ) {
  if (condition) {
    it = res.erase(it);
  } else {
    ++it;
  }
}

好的,它首先进行递增,在递增完成后进行比较。 - hidayat
1
不,hidayat;你的代码试图逐个删除向量中的所有元素。为此,你应该从res.begin()开始,然后永远不要推进迭代器,而是检索删除一个元素时返回的迭代器(对于所有STL容器都是如此)。增量本身就是错误的部分。 - Mephane
在实际代码中,我并不尝试删除所有元素,但是谢谢,现在我明白我做错了什么。 - hidayat
嗨,我正在以同样的方式进行,但仍然出现“out_of_range”错误。你能告诉我为什么吗? - DukeLover
@DukeLover,如果你没有看到任何代码,我只能猜测你需要在某个地方执行iterator++直到它等于.end()。如果你无法解决问题,或许可以提出一个问题? - Pieter

32
for( ; it != res.end();)
{
    it = res.erase(it);
}

或者更一般地说:

for( ; it != res.end();)
{
    if (smth)
        it = res.erase(it);
    else
        ++it;
}

5
为什么不使用 while 循环? - chamini2
@chamini2 在这种情况下,使用while循环会是等效的。 - glhrmv

3

因为vector中的erase方法返回传递迭代器的下一个迭代器。

我将举例说明如何在迭代时删除vector中的元素。

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it); // note it will = next(it) after erase
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

以下是输出结果:
024
024
024

一种更通用的方法:
template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

2

现代C++可以使用“std::remove_if”和lambda表达式来处理一些问题;

这段代码将从向量中删除“3”。

vector<int> vec {1,2,3,4,5,6};

vec.erase(std::remove_if(begin(vec),end(vec),[](int elem){return (elem == 3);}), end(vec));

1

it++指令在块的末尾执行。如果您要删除最后一个元素,那么尝试增加指向空集合的迭代器。


0

以下方法似乎也可以:

for (vector<int>::iterator it = res.begin(); it != res.end(); it++)
{
  res.erase(it--);
}

不确定这里有没有任何漏洞?


1
我对上面的代码不确定。我看到了3个主要问题。首先,在删除后,您没有将res.erase(it)签回到it。在删除东西时,您不能在迭代器语句中使用it++,因此您应该有一个条件检查来擦除它。如果条件失败,则应迭代到下一个(it ++)。虽然我想知道为什么你有it--?请原谅我,但为什么您甚至要减少迭代器?也许我会跌倒,如果是这种情况,我很抱歉。 - knoxgon
@SkippyleGrandGourou 感谢您的回复,我还没有找到与上面的减量状态相匹配的东西。在删除后迭代一步后退可能是这种情况吗?也许它与 it = res.erase(it) 相同?但我真的很怀疑。嗯嗯嗯 - knoxgon
根据Pieter的回答,"res.erase(it)总是返回下一个有效迭代器"。我猜it--it++相互抵消,所以据我理解,这段代码会一直删除(新的)第一个元素。虽然执行it--似乎不是个好主意,因为现在it是第一个元素... - Skippy le Grand Gourou
@SkippyleGrandGourou 哦,我明白了,这很有道理。不过当 it 是第一个元素时,it-- 似乎不安全,因为它会自动成为无效迭代器。感谢您的澄清。 - knoxgon
2
在传递给erase()函数之后,但在执行erase()函数之前,这会将迭代器减少。 - Elias
显示剩余6条评论

0
不要擦除然后增加迭代器。如果您的向量具有奇数(或偶数,我不知道)个元素,则无需增加,否则您将错过向量的末尾。

0

在 for 循环的循环表达式中,您将 it 递增到了(空)容器的末尾。


-1
作为对crazylammer答案的修改,我经常使用:
your_vector_type::iterator it;
for( it = res.start(); it != res.end();)
{
    your_vector_type::iterator curr = it++;
    if (something)
        res.erase(curr);
}

这样做的好处是您不必担心忘记增加迭代器,使得在具有复杂逻辑时更少出现错误。在循环内部,curr永远不会等于res.end(),并且无论您是否从向量中删除它,它都将位于下一个元素。


根据规范(c++11),这样做是不好的。在 erase 之后,每个迭代器和引用都会失效(23.3.6.5/3)。因此,在 erase 之后,你之前增加的那个迭代器也会失效。 - Håkon Egset Harnes
你能找到官方规范的参考资料吗?我认为那个网站是不准确的。 - Joseph Petroske
我能找到的最新公开可用的标准工作草案是c++11,http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf您可以在我原始评论中引用的位置找到该文本。 23.3.6.5/3“效果: 使迭代器和删除点之后的引用失效。” - Håkon Egset Harnes
@HåkonEgsetHarnes 那是一个 C++11 草案之前的版本。请参阅 https://dev59.com/wnVD5IYBdhLWcg3wHnyd#83763。 - M.M
这个答案中的代码是错误的,从向量中删除一个项会使得所有在erase点之后(包括it)的迭代器都无效。https://en.cppreference.com/w/cpp/container/vector/erase - M.M

-1
if(allPlayers.empty() == false) {
    for(int i = allPlayers.size() - 1; i >= 0; i--)
    {
        if(allPlayers.at(i).getpMoney() <= 0) 
            allPlayers.erase(allPlayers.at(i));
    }
}

这个方法对我很有效,并且不需要考虑已经删除的索引。


1
你怎么能说这个对你有效?你从来没有测试过。这甚至无法编译。allPlayers.at(i)不会返回一个迭代器。但是erase()需要一个迭代器。 - Elmue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接