【问题标题】:How to erase an vector element in this situation?在这种情况下如何擦除矢量元素?
【发布时间】:2014-07-20 16:24:25
【问题描述】:

我做了一个游戏,兰博发射子弹,子弹击中僵尸,我想从僵尸向量中删除被击中的僵尸。

这个嵌套循环一个一个地检查每个僵尸和子弹之间的碰撞。它在一段时间内工作得很好,但是当我开始杀死更多时,在某些时候它会崩溃,因为它想使用擦除僵尸的功能。

for ( it = zombies.begin(); it != zombies.end(); ++it ) {
    it->attack();
    for (rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it) {
        if(checkBasicCollision(it,rambo.it) && it != zombies.end()){
            zombies.erase(it);
        }
    }
}

我在zombies.erase(it); 之后添加了it--; 现在效果更好,但有时它仍然会崩溃。

我认为它的发生是这样的,例如有 5 个僵尸和 20 个子弹,僵尸迭代器在第二个僵尸处,第二个僵尸启动子弹循环以检查它是否被击中。循环开始,假设第三颗子弹击中僵尸,但循环仍在进行,即使僵尸被清除,它仍然继续循环。

我添加了中断;在zombies.erase(it); 之后,现在它没有任何问题。但是代码看起来很脏。是否有另一种方法可以轻松擦除当前元素

【问题讨论】:

  • 任何时候你最终编写了从序列容器中删除的代码,同时迭代同一个序列容器,是时候重新考虑你想要完成的事情了。您应该努力不编写从您正在迭代的同一容器中删除的代码。相反,尽可能使用remove(if)/erase 成语。您不仅会减少使迭代器失效的问题(通常没有问题),而且您已将代码重构为“逻辑上”您想要完成的内容。
  • 为什么不将僵尸存储在一个列表中呢?
  • 顺便问一下rambo.it的兴趣是什么?看起来这应该是一个临时变量。

标签: c++ vector iterator


【解决方案1】:

虽然提出了手动擦除的解决方案,但请注意它不是最惯用的解决方案。在惯用的 C++ 中,您可以像这样在擦除删除习语中使用 std::remove_if 算法:

// 1. A predicate that check whether a zombie was it by any bullet:
auto is_zombie_hit = [&rambo](Zombie const& zombie) {
    auto is_bullet_hitting_zombie = [&zombie](Bullet const& bullet) {
        return checkBasicCollision(zombie, bullet);
    };

    return std::any_of(
        rambo.bullets.begin(),
        rambo.bullets.end(),
        is_bullet_hitting_zombie
    );
};

// 2. Use the erase-remove idiom:
zombies.erase(
    std::remove_if(zombies.begin(), zombies.end(), is_zombie_hit),
    zombies.end()
);

注意:是的,您可以就地使用 lambda,但我更喜欢命名它们以表明它们的作用。

注意:这使用 C++11,但是用谓词替换 lambda 是微不足道的,any_of 的实现很容易生成,就像all_ofnone_of

【讨论】:

  • 这个 IMO 是最好的解决方案,特别是如果容器是一个序列容器,例如vector。每当我在遍历容器的循环中看到擦除代码时,我都会闻到代码气味。
【解决方案2】:

要使用erase,您需要使用返回的值并将其分配回迭代器,以便下一次迭代有效。

for ( it = zombies.begin(); it != zombies.end(); ) {
    it->attack();
    for (rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it) {
        if(checkBasicCollision(it,rambo.it) && it != zombies.end()){
            it = zombies.erase(it);    // erase will increment the iterator
        }
        else{
            ++it;    // no erase, increment the iterator manually
        }
    }
}

vector::erase 的文档中的返回值为:

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

【讨论】:

  • 那行不通。 it =zombies.erase(it) 将迭代器移动到下一个项目。并且 ++it 将再次移动迭代器。所以这将跳过被删除的项目之后的项目。
  • 我已经尝试过了,它不像菲利普所说的那样有效。
  • 请查看最新更新以了解如何处理这种情况。请注意,for 语句中不再有增量,而是在 if-else 块中处理。
  • 如果其中一颗子弹没有碰撞,它会掉到else挡块,并且不会检查其他子弹,因为僵尸会随着++it的变化而变化;
  • 如果最后一个僵尸与第一个子弹碰撞,这段代码将表现出未定义的行为。
【解决方案3】:

当擦除向量元素时,迭代器和索引变得无效。此外,对于 2 个或更多子弹击中同一个僵尸的情况(如果可能),您的代码也不正确。因为使用第二个子弹,内部循环将尝试擦除已经被击中的僵尸。相反,您应该这样做:

for ( uint i = 0; i < zombies.size(); ++i)
{
    for( auto it = rambo.bullets.begin(); it != rambo.bullets.end(); ++it)
    {
         if(checkBasicCollision(zombies[i], it)
         {
             zombies.erase( zombies.begin() + i );
             --i;
             break; // zombie is dead (well, technically it was already dead)
             // so no further checks are needed (i.e. exit inner loop)
         }
     }
}

【讨论】:

    【解决方案4】:

    大概是这样的:

    auto zombie_tail = std::remove_if(zombies.begin(), zombies.end(), [&](Zombie const & zombie) {
            zombie.attack();
            return std::any_of(rambo.bullets.begin(), rambo.bullets.end(), [&](Bullet const & bullet) {
                return checkBasicCollision(zombie, bullet);
            });
        });
    
    zombies.erase(zombie_tail, zombies.end());
    

    或者,如果您想远离 c++ 算法:

    for (it = zombies.begin(); it != zombies.end(); ) {
        it->attack();
    
        // Attempt to find a bullet that hit.
        for(rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it)
            if (checkBasicCollision(it, rambo.it))
                break;
    
        // Possibly remove the zombie, and advance the iterator
        if (rambo.it != rambo.bullets.end())
            it = zombies.erase(it);
        else
            ++it;
    }
    

    【讨论】:

      【解决方案5】:

      直接,易于阅读和掌握,但可能不是很花哨;

      for ( auto& z : zombies )
          z.attack();
      
      for( auto& b : rambo.bullets )
      {
          auto itr = zombies.begin();
          while( itr != zombies.end() )
          {
              if( checkBasicCollision(b,*itr) )
                  itr = zombies.erase(itr);
              else
                  ++itr;
          }
      }
      

      checkBasicCollision 现在接受引用,而不是迭代器

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-29
        • 2023-03-17
        • 1970-01-01
        相关资源
        最近更新 更多