如何通过引用正确传递迭代器?
How do I properly pass iterator by reference?
我有一个游戏,我在其中检查子弹和敌人之间的碰撞,我将其存储为 2 个矢量容器。人们说,如果你要在 for 循环中删除一个元素,你最好使用迭代器,我也这样做了。但是我现在在将迭代器传递给函数时遇到了问题。问题是我不一定需要擦除元素,所以它必须更复杂一些。
这是我检查碰撞的方式。 "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";
}
}
}
}
}
}
这是降低敌人生命值的方法:
void Map::TakeDamageByIndex(std::vector<Enemy>::iterator &itr, int damage)
{
(*itr).SetHealth((*itr).GetHealth() - damage);
}
删除项目符号的方法如下:
void Weapon::DeleteByIndex(std::vector<Bullet>::iterator &itr)
{
destroySprite((*itr).GetSprite());
bullets.erase(itr);
}
我敢肯定它看起来很糟糕而且不应该工作,但我不知道如何正确地做到这一点。请帮忙!
此外,当 for 循环使用索引(例如 bullets[i])时,这两种方法都可以正常工作,在这种情况下,问题是 "Vector subscript out of range" 错误。
在 DeleteByIndex()
中,更改为:
bullets.erase(itr);
为此:
itr = bullets.erase(itr);
std::vector::erase()
returns 指向已删除元素之后的 下一个 剩余元素的迭代器。下一个元素是您的外循环需要在下一次迭代中继续的地方。
因此,您需要将外循环从 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);
}
除了 Remy Lebeau 的回答之外还有一些其他评论。
按值传递 STL 迭代器与按引用传递一样有效,因此您需要按引用传递迭代器的唯一原因是:当您打算更改索引并且希望该更改在调用者的范围。 (例如,UTF-8 解析器需要消耗 1 到 4 个字节。)由于这段代码不需要这样做,所以最好只按值传递迭代器。
一般来说,如果您不修改通过引用传递的变量,则应该通过 const
引用来传递。在 Enemy::TakeDamage()
的情况下,您对迭代器所做的唯一事情就是取消引用它,因此您不妨传入一个 Enemy&
并使用 *i
作为参数调用它。
该算法效率不高:如果您删除列表开头附近的大量项目,则需要多次移动数组中所有剩余的项目。这会在 O(N²)
时间内运行。 std::list
虽然比std::vector
开销大,但可以在常数时间内删除元素,如果你有很多不在末尾的插入和删除,效率可能会更高。您还可以考虑仅将幸存的对象移动到新列表,然后销毁旧列表。至少这样,你只需要复制一次,你的通行证在O(N)
时间内运行。
如果您的容器存储指向对象的智能指针,您只需将指针移动到新位置,而不是整个对象。如果您的对象很小,这不会弥补大量堆分配的开销,但如果它们很大,则可以为您节省大量带宽。清除对它们的最后一次引用后,这些对象仍将被自动删除。
你可以这样做:
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;
}
使用擦除时要小心,因为它会改变向量的大小并使向量迭代器无效。
我有一个游戏,我在其中检查子弹和敌人之间的碰撞,我将其存储为 2 个矢量容器。人们说,如果你要在 for 循环中删除一个元素,你最好使用迭代器,我也这样做了。但是我现在在将迭代器传递给函数时遇到了问题。问题是我不一定需要擦除元素,所以它必须更复杂一些。
这是我检查碰撞的方式。 "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";
}
}
}
}
}
}
这是降低敌人生命值的方法:
void Map::TakeDamageByIndex(std::vector<Enemy>::iterator &itr, int damage)
{
(*itr).SetHealth((*itr).GetHealth() - damage);
}
删除项目符号的方法如下:
void Weapon::DeleteByIndex(std::vector<Bullet>::iterator &itr)
{
destroySprite((*itr).GetSprite());
bullets.erase(itr);
}
我敢肯定它看起来很糟糕而且不应该工作,但我不知道如何正确地做到这一点。请帮忙! 此外,当 for 循环使用索引(例如 bullets[i])时,这两种方法都可以正常工作,在这种情况下,问题是 "Vector subscript out of range" 错误。
在 DeleteByIndex()
中,更改为:
bullets.erase(itr);
为此:
itr = bullets.erase(itr);
std::vector::erase()
returns 指向已删除元素之后的 下一个 剩余元素的迭代器。下一个元素是您的外循环需要在下一次迭代中继续的地方。
因此,您需要将外循环从 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);
}
除了 Remy Lebeau 的回答之外还有一些其他评论。
按值传递 STL 迭代器与按引用传递一样有效,因此您需要按引用传递迭代器的唯一原因是:当您打算更改索引并且希望该更改在调用者的范围。 (例如,UTF-8 解析器需要消耗 1 到 4 个字节。)由于这段代码不需要这样做,所以最好只按值传递迭代器。
一般来说,如果您不修改通过引用传递的变量,则应该通过 const
引用来传递。在 Enemy::TakeDamage()
的情况下,您对迭代器所做的唯一事情就是取消引用它,因此您不妨传入一个 Enemy&
并使用 *i
作为参数调用它。
该算法效率不高:如果您删除列表开头附近的大量项目,则需要多次移动数组中所有剩余的项目。这会在 O(N²)
时间内运行。 std::list
虽然比std::vector
开销大,但可以在常数时间内删除元素,如果你有很多不在末尾的插入和删除,效率可能会更高。您还可以考虑仅将幸存的对象移动到新列表,然后销毁旧列表。至少这样,你只需要复制一次,你的通行证在O(N)
时间内运行。
如果您的容器存储指向对象的智能指针,您只需将指针移动到新位置,而不是整个对象。如果您的对象很小,这不会弥补大量堆分配的开销,但如果它们很大,则可以为您节省大量带宽。清除对它们的最后一次引用后,这些对象仍将被自动删除。
你可以这样做:
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;
}
使用擦除时要小心,因为它会改变向量的大小并使向量迭代器无效。