【问题标题】:How to remove elements from a vector that uses the vector size in the for loop如何从使用 for 循环中的向量大小的向量中删除元素
【发布时间】:2020-07-01 10:47:03
【问题描述】:

我有一个游戏,我向一个物体射击子弹,然后我删除被子弹击中的物体和屏幕外的子弹。

例如:

std::vector<object> object_list;
for(size_t i = 0; i < object_list.size(); i++)
{

    if(object_list[i].hit())
    {
        object_list.erase(object_list.begin() + i);
    }
    else
        object_list[i].draw();
}

这样做的问题是,当我删除一个对象时,向量的大小会减小,因此当它检查条件时,它会失败并且我会收到诸如“向量下标超出范围”之类的错误。我可以选择不通过渲染那些没有被击中的小行星来渲染小行星,但问题是没有。当被击中(分裂)时,物体的数量会增加,所以最终程序会变慢。我对屏幕外的子弹使用了类似的概念,但我找不到解决方法。我正在寻找解决此问题或删除元素的更好方法。

object 和 bullet 都是类。

【问题讨论】:

  • 我怀疑错误是在其他地方产生的 - 该循环应该在向量中留下太多元素。 (假设你有 v = {1,2,3,4, ...} 并且想要删除所有内容。你删除 v[0] 并拥有 v = {2,3,4, ...}。然后你删除 v[1] 并拥有 {2,4, ...}。等等。)
  • @furrylion 反向遍历向量。然后删除元素不会导致任何问题
  • 避免循环问题的保证方法是完全消除循环并使用标准算法。在这种情况下,std::remove_if(object_list.begin(), object_list.end(), [](const object &amp;o) {return o.hit();}) 可以解决问题(假设 C++11 及更高版本)。它还将避免您的循环遇到的问题,包括不检查/删除紧跟在已删除元素之后的元素。

标签: c++ vector


【解决方案1】:

你应该将 for 循环分成两部分:

  1. 删除所有“命中”元素:
object_list.erase(std::remove_if(object_list.begin(), 
object_list.end(), [](auto&& item) { return item.hit(); }),
object_list.end());
  1. 绘制剩余:
std::for_each(object_list.begin(), object_list.end(), [](auto&& item) { item.draw(); });

它更安全,更易读。

【讨论】:

    【解决方案2】:

    与其他答案的想法相同,但此代码使用迭代器更容易

    for (auto i = object_list.begin(); i != object_list.end(); )
    {
        if (i->hit())
        {
            i = object_list.erase(i);
        }
        else
        {
            i->draw();
            ++i;
        }
    }
    

    vector::erase 返回一个指向下一个元素的迭代器,您可以使用它来继续循环。

    【讨论】:

      【解决方案3】:

      使用 range-v3 库 (C++20) 的函数式方法

      [...] 我正在寻找解决这个或更好的移除元素的方法。

      使用range-v3 库中的ranges::actions::remove_if 操作,您可以使用函数式编程风格的方法来就地改变object_list 容器:

      object_list |= ranges::actions::remove_if(
          [](const auto& obj) { return obj.hit(); });
      

      随后调用ranges:for_each 来绘制对象:

      ranges::for_each(object_list, [](const auto& obj){ obj.draw(); });
      

      DEMO.

      【讨论】:

      • 这解决了为什么原始代码不能按预期工作的问题,只是一个副作用,但很好的解决方案;)
      • @codeling 它确实回答了 OP,但 OP 作者确实在某种程度上隐藏了我回答的他/她问题的一部分。我添加了来自 OP 的引述,强调了这个答案所针对的部分。
      • 你是对的!剩下的一个问题可能是 OP 的编译器是否已经支持 C++20,和/或如何启用它:D
      【解决方案4】:

      你可以这样做:

      for (size_t i = 0; i < object_list.size(); )
      {
          if (object_list[i].hit())
              object_list.erase(object_list.begin() + i)
          else
          {
              object_list[i].draw()
              ++i;
          }
      }
      

      【讨论】:

        【解决方案5】:

        假设你在 i=5 并且那个对象被击中了,删除那个元素后,i=6 的 obj 被转移到 i=5,你还没有检查它,所以只需添加 @987654321 @ 在你的擦除语句之后。

        另一种方法是 -

        for(size_t i = 0; i < object_list.size();)
        {
         if(object_list[i].hit())
         {
             object_list.erase(object_list.begin() + i);
         }
         else
         {
             object_list[i].draw();
             i++;
         }
        }
        

        此外,将对象从执行标记对象为命中的代码的向量中删除可能会更快,这样您只需要绘制列表中遗漏的所有对象。了解更多关于你如何做这一切的背景知识将有助于决定哪些具体的事情会更好:)

        【讨论】:

          【解决方案6】:

          显示的代码没有失败或给出超出范围的向量下标 - 它只是不考虑每个对象,因为它跳过了删除后的元素。

          有关采用 C++11 及更高版本概念的非常简短的解决方案,请参阅answer by Equodthe one by dfri

          为了更好地理解问题,和/或如果您必须坚持使用带有索引的 for 循环,您基本上有两种选择:

          1. 反向迭代向量(即从最后一个元素开始),然后在当前被移动的元素之后没有问题;
          for (int i=object_list.size()-1; i>=0; --i)
          {
              if (object_list[i].hit())
              {
                  object_list.erase(object_list.begin() + i)
              }
              else
              {
                  object_list[i].draw()
              }
          }
          
          1. 或者,如果顺序很重要(我可以想象要绘制的项目),并且您必须从前到后迭代,那么 只增加计数器 i 如果您有 没有删除当前元素:
          for (int i=0; i<object_list.size(); /* No increase here... */ )
          {
              if (object_list[i].hit())
              {
                  object_list.erase(object_list.begin() + i);
              }
              else
              {
                  object_list[i].draw();
                  ++i;   // ...just here if we didn't remove the element
              }
          }
          

          【讨论】:

            【解决方案7】:

            我怀疑std::vector 不是您想要的容器(当然,我不知道整个代码)。每次对擦除的调用都意味着重新分配向量的右侧部分(然后是对象的副本),这可能会非常昂贵。而您的实际问题是设计问题的症状。

            在我看来,std::list 可能更好:

            std::list<object> objects;
            // ...
            for(std::list<object>::iterator it = objects.begin(); it != objects.end();)
            {
                if(it->hit())
                  objects.erase(it++); // No object copied
                else
                {
                  it->draw();
                  ++it;
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2015-02-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-01-27
              • 1970-01-01
              相关资源
              最近更新 更多