【问题标题】:Expression: vector subscript out of range表达式:向量下标超出范围
【发布时间】:2019-08-10 01:42:24
【问题描述】:

我正在通过SFML制作一个简单的游戏,遇到以下错误:

调试断言失败! 表达式:向量下标超出范围

此错误仅在“火箭”与第一个“敌人”接触而屏幕上同时有 3 个敌人时出现。我知道这与我在 for 循环中从向量中删除元素这一事实有关,但我能否明确回答为什么此错误仅在屏幕上有 3 个或更多敌人时发生。另外,我怎样才能重写我的代码,以便我可以从敌人向量中删除“敌人”元素而不会导致错误?提前致谢

    for (int i = 0; i < rockets.size(); i++) //This is where the error points to
    {
        for (int j = 0; j < enemies.size(); j++)
        {
            Rocket *rocket = rockets[i];
            Enemy *enemy = enemies[j];

            if (checkCollision(rocket->getSprite(), enemy->getSprite()))
            {
                //hitSound.play();
                score++;
                std::string finalScore = "Score: " + std::to_string(score);
                scoreText.setString(finalScore);
                sf::FloatRect scoreBounds = scoreText.getGlobalBounds();
                scoreText.setOrigin(scoreBounds.width / 2, scoreBounds.height / 2);
                scoreText.setPosition(viewSize.x * 0.5f, viewSize.y * 0.10f);



                rockets.erase(rockets.begin() + i);
                enemies.erase(enemies.begin() + j);

                delete(rocket);
                delete(enemy);
                if (score % 5 == 0) levelUp();
                printf("rocket intersects with enemy \n");
            }
        }
    }

我已将敌人和火箭对象存储在向量中:

    std::vector <Enemy*> enemies;
    std::vector <Rocket*> rockets;

如果有帮助,这是我的整个更新功能:

void update(float dt)
{
    hero.update(dt);

    currentTime += dt;
    if (currentTime >= prevTime + 1.125f)
    {
        spawnEnemy();
        prevTime = currentTime;
    }

    for (int i = 0; i < enemies.size(); i++)
    {
        Enemy *enemy = enemies[i];
        enemy->update(dt);

        if (enemy->getSprite().getPosition().x < 0)
        {
            enemies.erase(enemies.begin() + i);
            delete(enemy);
            gameOver = true;
            gameOverSound.play();
        }
    }

    for (int i = 0; i < rockets.size(); i++)
    {
        Rocket *rocket = rockets[i];

        rocket->update(dt);

        if (rocket->getSprite().getPosition().x > viewSize.x)
        {
            rockets.erase(rockets.begin() + i);
            delete(rocket);
        }
    }

    for (int i = 0; i < rockets.size(); i++)
    {
        for (int j = 0; j < enemies.size(); j++)
        {
            Rocket *rocket = rockets[i];
            Enemy *enemy = enemies[j];

            if (checkCollision(rocket->getSprite(), enemy->getSprite()))
            {
            //hitSound.play();
                score++;
                std::string finalScore = "Score: " + std::to_string(score);
                scoreText.setString(finalScore);
                sf::FloatRect scoreBounds = scoreText.getGlobalBounds();
                scoreText.setOrigin(scoreBounds.width / 2, scoreBounds.height / 2);
                scoreText.setPosition(viewSize.x * 0.5f, viewSize.y * 0.10f);



                rockets.erase(rockets.begin() + i);
                enemies.erase(enemies.begin() + j);

                delete(rocket);
                delete(enemy);
                if (score % 5 == 0) levelUp();
                printf("rocket intersects with enemy \n");
            }
        }
    }

    for (int i = 0; i < enemies.size(); i++)
    {
        Enemy *enemy = enemies[i];

        if (checkCollision(enemy->getSprite(), hero.getSprite()))
        {
            gameOver = true;
            gameOverSound.play();
        }
    }
}

【问题讨论】:

  • 如果irockets.size() - 1 并且j 尚未到达enemies.size()-1,请考虑内循环会发生什么。如果内循环删除了rockets[i]enemies[j],内循环体将再次执行并访问rockets不存在的元素。这会导致未定义的行为。 if 然后取消引用 rockets[i] (更多未定义的行为)并且,如果测试为真,则删除它 - 这几乎肯定会破坏程序使用的内存。
  • 一般来说,尽量避免擦除正在迭代的容器中的元素。您有 erase/remove 习惯用法,它允许擦除符合特定条件的项目,而无需编写擦除循环。
  • 另外,您可以将对象标记为“已死”,而不是实际删除它们。然后在循环之后,只需使用erase/remove_if 删除死对象。这需要为每个类添加一个bool 成员。
  • 另外,为了回答您的问题,我们想看看RocketEnemy 的定义是什么。

标签: c++ visual-studio sfml


【解决方案1】:

另外,我怎样才能重写我的代码,以便我可以删除“敌人” 来自敌人向量的元素不会导致错误?

如果允许您更改RocketEnemy,最简单的更改是添加一个bool 成员来表示对象是活着还是死了。然后只需将对象标记为已死,而无需实际从容器中删除对象。然后在循环之后,删除死对象。

EnemyRocket 类所需的更改是添加一个 bool 成员,表示是否需要删除对象:

class Enemy
{
   bool destroyed;
   //...
   public:
        Enemy() : destroyed(false) {}
        bool is_destroyed() const { return destroyed; }
        void set_destroyed() { destroyed = true; }
};

class Rocket
{
   bool destroyed;
   //...
   public:
        Rocket() : destroyed(false) {}
        bool is_destroyed() const { return destroyed; }
        void set_destroyed() { destroyed = true; }
};

更改完成后,以下是主循环的可能代码更改:

#include <algorithm>
//...
for (int i = 0; i < rockets.size(); i++) 
{
    if ( rockets[i]->is_destroyed() )  // skip if this was destroyed
       continue;        
    for (int j = 0; j < enemies.size(); j++)
    {
        if (enemies[j]->is_destroyed())  // skip if this was destroyed
           continue;
        Rocket *rocket = rockets[i];
        Enemy *enemy = enemies[j];
        if (checkCollision(rocket->getSprite(), enemy->getSprite()))
        {
            //hitSound.play();
            score++;
            std::string finalScore = "Score: " + std::to_string(score);
            scoreText.setString(finalScore);
            sf::FloatRect scoreBounds = scoreText.getGlobalBounds();
            scoreText.setOrigin(scoreBounds.width / 2, scoreBounds.height / 2);
            scoreText.setPosition(viewSize.x * 0.5f, viewSize.y * 0.10f);

            // "destroy" these items
            rocket->set_destroyed();
            enemy->set_destroyed();

            if (score % 5 == 0) levelUp();
            printf("rocket intersects with enemy \n");
            break; // since the rocket is destroyed, 
        }
    }
}

// remove the dead items

// first partition the dead items off from the live items
auto iter = std::stable_partition(rockets.begin(), rockets.end(), [](rocket *r) 
                                  {return r->is_destroyed();});

// issue a delete call on these items.
std::for_each(rockets.begin(), iter, [](rocket *r) { delete r; });

// now erase from the container
rockets.erase(rockets.begin(), iter);

// do similar for the enemies container
auto iter2 = std::stable_partition(enemies.begin(), enemies.end(), [](enemy *e) 
                                  {return e->is_destroyed();});
std::for_each(enemies.begin(), iter2, [](enemy *e) { delete e; });
enemies.erase(enemies.begin(), iter2);

基本上我们标记要销毁的项目,然后在循环完成后处理它们。我们处理它们的方式是使用std::stable_partition对dead items进行分区,对分区左侧的每个item调用delete,最后将它们从容器中擦除。

我没有简单地使用std::remove_if 的原因是remove_if 算法使要删除的项目无效,因此尝试发出delete 调用将导致未定义的行为。因此,首先将“死”项目分区,然后delete-ing 他们,然后最后擦除它们 IMO 更安全。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多