矢量擦除迭代器


75

我有这个代码:

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;
    }
}

“一个随机访问迭代器,指向在函数调用删除的最后一个元素之后的元素的新位置,如果操作删除了序列中的最后一个元素,则该向量为向量结束。”

此代码崩溃,但是如果我使用该if(it == res.end())部分然后返回,则可以正常工作。怎么来的?for循环是否缓存,res.end()以便不等于运算符失败?



7
因为这只是代码的简化,所以我不打算删除真实代码中的所有元素
hidayat 2011年

Answers:


151

res.erase(it) 始终返回下一个有效的迭代器,如果您删除最后一个元素,它将指向 .end()

在循环的末尾++it总是被调用,因此您.end()不允许递增。

简单地检查.end()仍然会留下一个错误,因为您总是在每次迭代中跳过一个元素(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 2011年

1
不,hidayat;您的代码试图一一删除向量中的所有元素。为此,您应该从res.begin()迭代器开始,然后再不进行迭代,而应在擦除元素时检索返回的迭代器(所有STL容器也是如此)。增量本身就是错误的部分。
Mephane 2011年

在真实的代码中,我并没有试图删除所有元素,但是,谢谢,我了解我现在做错了什么
hidayat 2011年

嗨,我以相同的方式做,但仍然出现“ out_of_range”错误。你能告诉我为什么吗?
DukeLover

@DukeLover,您必须iterator++在等于.end()某个位置的时间内执行一会儿,而看不到我能猜到的所有代码。如果您不明白,也许提出一个问题?
彼得

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

或更笼统地说:

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

3
为什么不使用while
chamini2

@ chamini2while在这种情况下,循环是等效的。
glhrmv

3

因为向量中的方法擦除会返回传递的迭代器的下一个迭代器。

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

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;

}

在下面输出aw:

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;    
}


1

您可以使用现代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));

0

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


0

您会增加itfor循环的循环表达式中(空)容器的末尾。


-1

以下似乎也起作用:

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

不知道这是否有任何缺陷?


尽管此代码可以回答问题,但最好解释一下它的作用并为其添加一些引用。
Hamid Pourjam 2015年

1
我不确定上面的代码。我看到了3个主要问题。首先,it删除后不要将res.erase(it)重新签回。删除内容时,您不得在it ++语句中放入iterator语句,因此应进行条件检查以擦除它。如果条件失败,则应迭代到下一个(it++)。虽然我不知道你为什么要这么做it--?请原谅我,但为什么还要减少迭代器?如果是这样的话,我也许会迷迷糊糊。
VolkanGüven17年

@VG谢谢,我想您的评论解决了答案中的问题,因此使其具有教育意义,也许值得一提。恐怕我也不再理解两者的逻辑,it--从那以后桥下流了太多的水……
Skippy le Grand Gourou

@SkippyleGrandGourou感谢您的答复,我还没有真正找到与上面的递减状态匹配的东西。在删除后迭代一步是否可行?也许是一样的it = res.erase(it)?虽然我真的对此表示怀疑。Hmmmm
沃尔坎而且Güven

@VG:根据Pieter的回答,“res.erase(it)总是返回下一个有效的迭代器”。我猜测it--it++取消,因此据我所知,这段代码一直在擦除(新的)第一个元素。表演it--似乎并不是一个好主意,但it现在已成为第一个要素……
Skippy le Grand Gourou

-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));
    }
}

这对我有用。而且,无需考虑索引已被删除。


您怎么说这对您有用?您从未测试过。这甚至不编译。alllPlayers.at(i)不返回迭代器。但是delete()需要一个迭代器。
Elmue

-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),这并不酷。擦除点之后的每个迭代器和引用都无效(23.3.6.5/3)因此,它(在擦除前增加的值)在擦除后无效。来源:kera.name/articles/2011/06/iterator-invalidation-rules-c0x
哈康Egset阿尔讷

您能找到提到官方说明的参考吗?我认为该网站不准确
约瑟夫·彼得罗斯克

我可以找到的针对该标准的最新的公开可用工作草案是针对c ++ 11的,open-std.org / jtc1 / sc22 / wg21 / docs / papers / 2011 / n3242.pdf 您可以在我所引用的位置找到文本在我的原始评论中。23.3.6.5/3“功效:迭代器的失效和引用在或擦除后点”
哈康Egset阿尔讷

@HåkonEgsetHarnes是C ++ 11之前的草案。参见stackoverflow.com/questions/81656/…–
MM

此答案中的代码是错误的,从向量中删除一项会在擦除时及以后(包括it)时使所有迭代器无效。en.cppreference.com/w/cpp/container/vector/erase
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.