如何正确地通过引用传递迭代器?

时间:2018-12-03 23:38:26

标签: c++ visual-c++ vector iterator stdvector

我有一个游戏,检查子弹与敌人之间的碰撞,并将其存储为2个矢量容器。人们说,如果您要删除for循环中的元素,则最好使用迭代器,所以我做到了。但是现在我有一个将迭代器传递给函数的问题。问题是我不必删除元素,因此它必须更复杂一些。

这是我检查碰撞的方法。 “ CircularCollision”工作正常,没有错误。

restart()

这是应该减少敌人生命值的方法:

void ResolveColision(Weapon &weap, Map &map)
{
    std::vector<Bullet> bullets = weap.GetBullets();

    if (!bullets.empty())
    {
        for (std::vector<Bullet>::iterator i = bullets.begin(); i != bullets.end(); ++i)
        {
            std::vector<Enemy> enemies = map.GetEnemies();

            if (!enemies.empty())
            {
                for (std::vector<Enemy>::iterator j = enemies.begin(); j != enemies.end(); ++j)
                {
                    if (CircularCollision((*i), (*j)))
                    {
                        weap.DeleteByIndex(i);
                        map.TakeDamageByIndex(j, weap.GetDamage());
                        std::cout << "HIT!\n";
                    }
                }
            }
        }
    }
}

以下是删除项目符号的方法:

void Map::TakeDamageByIndex(std::vector<Enemy>::iterator &itr, int damage)
{
   (*itr).SetHealth((*itr).GetHealth() - damage);
}

我确定它看起来很恐怖,并且不起作用,但我不知道如何正确执行。请帮忙! 另外,当for循环使用索引(例如bullets [i])进行操作时,这两种方法都可以正常工作,在这种情况下,问题出在“向量下标超出范围”错误。

3 个答案:

答案 0 :(得分:4)

DeleteByIndex()中,更改以下内容:

bullets.erase(itr);

对此:

itr = bullets.erase(itr);

std::vector::erase()将迭代器返回到被擦除元素之后的 next 剩余元素。下一个元素是您的外循环在下一个迭代中需要继续的地方。

这样,您需要将外部循环从for更改为while,否则您将跳过元素(实际上,当您仍然在使用索引):

void ResolveColision(Weapon &weap, Map &map)
{
    std::vector<Bullet> bullets = weap.GetBullets();

    std::vector<Bullet>::iterator bullerItr = bullets.begin();
    while (bullerItr != bullets.end())
    {
        std::vector<Enemy> enemies = map.GetEnemies();
        bool wasAnyHit = false;

        for (std::vector<Enemy>::iterator enemyItr = enemies.begin(); enemyItr != enemies.end(); ++enemyItr)
        {
            if (CircularCollision(*bulletItr, *enemyItr))
            {
                wasAnyHit = true;
                weap.DeleteByIndex(bulletItr);
                map.TakeDamageByIndex(enemyItr, weap.GetDamage());
                std::cout << "HIT!\n";
                break;
            }
        }

        if (!wasAnyHit)
            ++bulletItr;
    }
}

话虽这么说,我建议用std::find_if()代替内部循环。并重命名DeleteByIndex()TakeDamageByIndex(),因为它们不再使用索引了。实际上,我根本不会将迭代器传递给TakeDamage...(),而是传递实际的Enemy对象。或者,最好将TakeDamage()本身移到Enemy中。

尝试更多类似的方法:

void ResolveColision(Weapon &weap, Map &map)
{
    auto bullets = weap.GetBullets();

    auto bulletItr = bullets.begin();
    while (bulletItr != bullets.end())
    {
        auto enemies = map.GetEnemies();
        auto &bullet = *bulletItr;

        auto enemyHit = std::find_if(enemies.begin(), enemies.end(),
          [&](Enemy &enemy){ return CircularCollision(bullet, enemy); }
        );

        if (enemyHit != enemies.end())
        {
            weap.DeleteBulletByIterator(bulletItr);
            enemyHit->TakeDamage(weap.GetDamage());
            std::cout << "HIT!\n";
        }
        else
            ++bulletItr;
    }
}

void Enemy::TakeDamage(int damage)
{
   SetHealth(GetHealth() - damage);
}

void Weapon::DeleteBulletByIterator(std::vector<Bullet>::iterator &itr)
{
    destroySprite(itr->GetSprite());
    itr = bullets.erase(itr);
}

答案 1 :(得分:1)

除了雷米·勒博(Remy Lebeau)的答案以外,还有其他评论。

按值传递STL迭代器和按引用传递STL迭代器一样有效,因此您需要按引用传递一个STL迭代器的唯一原因是:当您打算更改索引并且希望该更改在调用者的范围内可见。 (例如,UTF-8解析器需要消耗1到4个字节的任何空间。)由于此代码不需要这样做,因此最好只按值传递迭代器。

通常,如果您不修改通过引用传递的变量,则应改为通过const引用传递。对于Enemy::TakeDamage(),您对迭代器所做的唯一事情就是取消对其的引用,因此您最好传入Enemy&并以*i作为参数来调用它。 / p>

该算法不是很有效:如果删除列表开头附近的很多项目,则需要多次移动数组中所有剩余的项目。这是在O(N²)时间运行的。尽管std::list的开销比std::vector高,但它可以在恒定时间内删除元素,并且如果您有很多插入和删除操作都没有结束的话,则效率可能更高。您可能还考虑仅将可以幸存的对象移动到新列表,然后销毁旧列表。至少通过这种方式,您只需要复制一次,您的通行证就会在O(N)时间内运行。

如果您的容器存储了指向对象的智能指针,则只需要将指针移动到新位置,而不是整个对象。如果对象很小,这将无法弥补大量堆分配的开销,但是如果对象很大,则可以节省大量带宽。清除最后一次引用后,这些对象仍将自动删除。

答案 2 :(得分:0)

您可以执行以下操作:

MyLibrary.dll

使用擦除时要小心,因为这会改变向量的大小,并使向量迭代器无效。