C++中vector删除元素的坑

最近遇到vector删除元素的一些坑。。总结一下原因,分享一些解决办法。 0.首先说说我遇到这个问题时的情况(以下为我的错误代码)

void ParticleSystem::update()
{
    for(vector<Particle*>::iterator iter = particles.begin();iter != particles.end();iter++)
    {
        (*iter)->update();
        if((*iter)->isDead())
          particles.erase(iter);
    }
}

这个错误在于迭代器的使用错误。报错是超出了容器的访问范围。 【正确的使用方法】: 1.下面这种方法虽然这解决了上面的bug,但是感觉很蠢。

for(int i = particles.size() - 1; i >=0; i--)
{
    particles[i]->update();
    if(particles[i]->isDead())
    {
        vector<Particle*>::iterator iter = particles.begin() + i;
        particles.erase(iter);
    }
}

2.上面一直困在要使用迭代器的思路中,其实vector是连续内存存储的,感觉不用迭代器就完全能实现自己的需求。

for(int i = particles.size() - 1; i >=0; i--)
{
    particles[i]->update();
    if(particles[i]->isDead())
        particles.erase(particles.begin() + i);
}

3.然后我就想。。既然不用迭代器就如此方便,对初学者来说也一直习惯于定义一个i从头到尾遍历数组这种简单的方法,那为什么官方还要鼓励用迭代器(iterator)遍历动态数组(vector)呢?     于是我们先来了解一下iterator:iterator的概念源自于对遍历一个线性容器工具的抽象,即如何你能访问这个容器的某个元素。对于最简单的数组,当然可以用数组的索引值,因为数组是连续存放在内存中的;但对于链表,就必须用指针。除此之外,还有还有很多种数据结构需要提供一个方便的工具来访问其中的元素,方法有ID,关键字等等。为了统一所有的容器的这种工具的使用,一般提供一整套容器的开发者就会用一种方式来表示各种容器的访问工具。 开发者都站在大局,秉承通用性这一原则来设计。就像爱因斯坦全力以赴去探索统一场论一样。     那么我们也来学习官方推荐的方法吧。

void ParticleSystem::update()
{
    for(vector<Particle*>::iterator iter = particles.begin();iter != particles.end();)
    {
        (*iter)->update();
        if((*iter)->isDead())
            iter = particles.erase(iter); 
        else
            iter++;
    }
}

那么我们来看看这个方法3和我原来的错误代码0相差有什么区别呢? 搜索之后,发现: iterator erase (iterator position);  //这个函数删除指定元素,返回指向删除元素(或范围)的下一个元素。 搜索的时候,发现也有人有同样的疑问,错误代码长这样子:

void ParticleSystem::update()
{
    for(vector<Particle*>::iterator iter = particles.begin();iter != particles.end();iter++) 
    { 
        (*iter)->update(); 
        if((*iter)->isDead())
            particles.erase(iter--); 
    }
}

这么写的理解应该是:erase完之后,iter指向下一个元素,那么我在删除完之后让他自减不就回来了嘛?但是这样也报错。
原因:particles.erase(iter--)这句话出了问题。因为在执行完particles.erase(iter)之后,iter就已经失效了,所以他并不能自减了。咱们就老老实实用返回值吧。

PS.1.以后遇到问题还是及时查文档..有时候百度有坑..自己瞎yy就更不可信了… 2.由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。


其实以上的方法来来去去都是一个路子。嘉文大大提供了不同的角度。 4.运用break

void ParticleSystem::update()
{
    for(vector<Particle*>::iterator iter = particles.begin();iter != particles.end();iter++)
    {
        (*iter)->update();
        if((*iter)->isDead())
        {
            particles.erase(iter);
            break;
        }
    }
}

其实我一开始感觉很奇怪,并不认可这种方法。但是细想还是有以下优点:

1.这种方法其实比方法3更节省时间:方法3每一帧要跑一整个数组,但这种方法碰到要删除的就break。每一帧占的时间其实比较短。

2.遇到非常大的数组的话:如果数组长度非常大,那么方法3就会出现卡顿的情况,而这个方法不会。

这个方法也有如下缺点:

1.内存占用时间会稍微长一些。毕竟一帧才删除一个数据,删除整个数组的总时长就会增加一些。

2.如果有特殊需求,比如你就是需要把所有需要删除的东西都删除才能执行下一步的操作时,这个方法就不适用了。所以其实还是应该根据具体需求用不同的方法。

3.新建另一个容器。 在增删查改特别复杂的时候,容器长度也不稳定,迭代器处理起来非常麻烦的时候,不如直接新建另外一个容器,把有用的数据挪进去,删掉原来的容器来的干净一些。

Tags:,
4 Comments

Add a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注