简体   繁体   English

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

[英]How do I properly pass iterator by reference?

I have a game where I check collision between bullets and enemies which I store as 2 vector containers. 我有一个游戏,我检查子弹和敌人之间的碰撞,我存储为2个矢量容器。 People say if you're gonna erase an element in the for loop you better use iterators and so I did. 人们说如果你要删除for循环中的元素,你最好使用迭代器,所以我做了。 But I have a problem now with passing the iterator to a function. 但我现在有一个问题,即将迭代器传递给函数。 The thing is I don't necessarily need to erase the element so it has to be a bit more complex. 问题是我不一定需要擦除元素,所以它必须更复杂一些。

This is the way I check collision. 这是我检查碰撞的方式。 "CircularCollision" works fine, no mistakes there. “CircularCollision”工作正常,没有错误。

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

Here's the method which is supposed to decrease the health of an enemy: 这是减少敌人健康的方法:

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

Here's the method which deletes the bullet: 这是删除子弹的方法:

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

I'm sure it looks horrible and it shouldn't work but I have no idea how to do it properly. 我确定它看起来很可怕而且应该不起作用,但我不知道如何正确地做到这一点。 Please help! 请帮忙! Also, both methods work properly when the for loops operate with indexes (eg bullets[i]), in that case the problem is with "Vector subscript out of range" error. 此外,当for循环使用索引(例如bullets [i])时,两种方法都能正常工作,在这种情况下,问题是“向量下标超出范围”错误。

In DeleteByIndex() , change this: DeleteByIndex() ,更改此:

bullets.erase(itr);

To this: 对此:

itr = bullets.erase(itr);

std::vector::erase() returns an iterator to the next remaining element after the element that was erased. std::vector::erase()将一个迭代器返回到被删除元素之后的下一个剩余元素。 That next element is where your outer loop needs to continue from on its next iteration. 下一个元素是外循环在下一次迭代时需要继续的位置。

As such, you need to change your outer loop from a for to a while instead, or else you will skip elements (in fact, your original code suffers from that problem when you were still using indexes): 因此,您需要将外部循环从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;
    }
}

That being said, I would suggest replacing the inner loop with std::find_if() instead. 话虽这么说,我建议用std::find_if()代替内部循环。 And renaming DeleteByIndex() and TakeDamageByIndex() since they don't take an index anymore. 并重命名DeleteByIndex()TakeDamageByIndex()因为它们不再采用索引。 In fact, I would not pass an iterator to TakeDamage...() at all, pass the actual Enemy object instead. 事实上,我不会将迭代器传递给TakeDamage...() ,而是传递实际的Enemy对象。 Or better, move TakeDamage() into Enemy itself. 或者更好的是,将TakeDamage()移入Enemy本身。

Try something more like this: 尝试更像这样的东西:

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

A few other comments in addition to Remy Lebeau's answer. 除了Remy Lebeau的回答之外,还有其他一些评论。

It's as efficient to pass a STL iterator by value as by reference, so the only reason you would need to pass one by reference is: when you intend to change the index and you want that change to be visible in the caller's scope. 通过引用将值传递给STL迭代器同样有效,因此您需要通过引用传递一个的唯一原因是:当您打算更改索引并希望该更改在调用者的作用域中可见时。 (For example, a UTF-8 parser needs to consume anywhere from one to four bytes.) Since this code doesn't need to do that, you're better off just passing the iterator by value. (例如,UTF-8解析器需要消耗1到4个字节的任何位置。)由于此代码不需要这样做,因此最好只按值传递迭代器。

In general, if you aren't modifying the variable you pass by reference, you should pass by const reference instead. 通常,如果您没有修改通过引用传递的变量,则应该通过const引用来传递。 In the case of Enemy::TakeDamage() , the only thing you do with the iterator is dereference it, so you might as well just pass in an Enemy& and call it with *i as the parameter. Enemy::TakeDamage()的情况下,你对迭代器做的唯一事情就是取消引用它,所以你也可以传入一个Enemy&并用*i作为参数来调用它。

The algorithm is not very efficient: if you delete a lot of items near the start of the list, you would need to move all remaining items of the array multiple times. 该算法效率不高:如果删除列表开头附近的大量项目,则需要多次移动该阵列的所有剩余项目。 This runs in O(N²) time. 这在O(N²)时间内运行。 A std::list , although it has a high overhead compared to std::vector , can delete elements in constant time, and might be more efficient if you have a lot of insertions and deletions that are not at the end. std::list虽然与std::vector相比具有较高的开销,但可以在常量时间内删除元素,如果你有很多插入和删除不在最后,可能会更有效。 You might also consider moving only the objects that survive to a new list and then destroying the old one. 您也可以考虑仅将幸存的对象移动到新列表,然后销毁旧列表。 At least this way, you only need to copy once, and your pass runs in O(N) time. 至少这样,你只需要复制一次,你的传递在O(N)时间运行。

If your containers store smart pointers to the objects, you only have to move the pointers to a new location, not the entire object. 如果容器存储指向对象的智能指针,则只需将指针移动到新位置,而不是整个对象。 This will not make up for the overhead of lots of heap allocations if your objects are small, but could save you a lot of bandwidth if they are large. 如果对象很小,这将无法弥补大量堆分配的开销,但如果它们很大,可以为您节省大量带宽。 The objects will still be automatically deleted when the last reference to them is cleared. 清除对它们的最后一次引用时,仍将自动删除这些对象。

You could do something like this: 你可以这样做:

void delByIndex(vector<int>::iterator &i, vector<int>& a)
{
    a.erase(i);
}   

int main()
{

    vector<int> a {1,5,6,2,7,8,3};
    vector<int> b {1,2,3,1};

    for(auto i=a.begin();i!=a.end();)
    {
        bool flag = false;
        for(auto j=b.begin();j!=b.end();j++)
            {
                if(*i==*j)
                {                    
                    flag = true;
                }
            }
            if(flag)
            {
                delByIndex(i, a);
            }
            else
                i++;

    }

    for(auto i:a)    
        cout << i << " ";


    return 0;
}

Be careful when using erase as it will change the size of the vector and also invalidates the vector iterator. 使用erase时要小心,因为它会改变向量的大小并使向量迭代器无效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM