115

我想使用擦除方法从向量中清除一个元素。但这里的问题是,不能保证元素在向量中只出现一次。它可能出现多次,我需要清除所有这些。我的代码是这样的:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    std::vector<int>::iterator endIter = myNumbers_in.end();
    for(; iter != endIter; ++iter)
    {
        if(*iter == number_in)
        {
            myNumbers_in.erase(iter);
        }
    }
}

int main(int argc, char* argv[])
{
    std::vector<int> myNmbers;
    for(int i = 0; i < 2; ++i)
    {
        myNmbers.push_back(i);
        myNmbers.push_back(i);
    }

    erase(myNmbers, 1);

    return 0;
}

这段代码显然会崩溃,因为我在迭代它时更改了向量的结尾。实现这一目标的最佳方法是什么?即有没有办法做到这一点而无需多次迭代向量或创建一个向量的更多副本?

4

5 回答 5

188

使用删除/擦除习语

std::vector<int>& vec = myNumbers; // use shorter name
vec.erase(std::remove(vec.begin(), vec.end(), number_in), vec.end());

发生的情况是压缩与开头的remove要删除的值 ( ) 不同的元素,并将迭代器返回到该范围之后的第一个元素。然后删除这些元素(其值未指定)。number_invectorerase

于 2008-12-07T11:07:10.887 回答
64

调用擦除将使迭代器无效,您可以使用:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    while (iter != myNumbers_in.end())
    {
        if (*iter == number_in)
        {
            iter = myNumbers_in.erase(iter);
        }
        else
        {
           ++iter;
        }
    }

}

或者您可以将std::remove_if与仿函数和 std::vector::erase 一起使用:

struct Eraser
{
    Eraser(int number_in) : number_in(number_in) {}
    int number_in;
    bool operator()(int i) const
    {
        return i == number_in;
    }
};

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), Eraser(number_in)), myNumbers.end());

在这种情况下,您可以使用std::remove,而不是编写自己的仿函数:

std::vector<int> myNumbers;
myNumbers.erase(std::remove(myNumbers.begin(), myNumbers.end(), number_in), myNumbers.end());

在 C++11 中,您可以使用 lambda 而不是仿函数:

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), [number_in](int number){ return number == number_in; }), myNumbers.end());

在 C++17 中std::experimental::erasestd::experimental::erase_if也可用,在 C++20 中,它们(最终)重命名为std::erasestd::erase_if注意:在 Visual Studio 2019 您需要将您的 C++ 语言版本更改为最新的实验版本以获得支持):

std::vector<int> myNumbers;
std::erase_if(myNumbers, Eraser(number_in)); // or use lambda

或者:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);
于 2008-12-07T10:22:53.987 回答
15
  1. 您可以使用索引访问进行迭代,

  2. 为了避免 O(n^2) 复杂性,您可以使用两个索引,i - 当前测试索引,j - 存储下一项的索引,并在循环结束时使用新的向量大小。

代码:

void erase(std::vector<int>& v, int num)
{
  size_t j = 0;
  for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] != num) v[j++] = v[i];
  }
  // trim vector to new size
  v.resize(j);
}

在这种情况下,您没有使迭代器失效,复杂度为 O(n),并且代码非常简洁,您不需要编写一些辅助类,尽管在某些情况下使用辅助类可以受益于更灵活的代码。

此代码不使用erase方法,但可以解决您的任务。

使用纯 stl 您可以通过以下方式执行此操作(这类似于 Motti 的答案):

#include <algorithm>

void erase(std::vector<int>& v, int num) {
    vector<int>::iterator it = remove(v.begin(), v.end(), num);
    v.erase(it, v.end());
}
于 2008-12-07T11:44:23.013 回答
4

根据您这样做的原因,使用std::set可能比 std::vector 更好。

它允许每个元素只出现一次。如果您多次添加它,无论如何都只会删除一个实例。这将使擦除操作变得微不足道。擦除操作的时间复杂度也将低于向量,但是,在集合上添加元素的速度较慢,因此它可能没有太大优势。

如果您对元素被添加到向量中的次数或元素添加的顺序感兴趣,这当然不会起作用。

于 2008-12-07T11:18:49.040 回答
1

C++20以来就有std::erase 和 std::erase_if,它们结合了 remove-erase 习语。

std::vector<int> nums;
...
std::erase(nums, targetNumber);

或者

std::vector<int> nums;
...
std::erase_if(nums, [](int x) { return x % 2 == 0; }); 
于 2022-01-11T01:51:23.027 回答