如何按索引从std :: vector <>擦除元素?


508

我有一个std :: vector <int>,我想删除第n个元素。我怎么做?

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

vec.erase(???);

4
考虑使用提供两端插入和删除功能的std :: deque。
达里奥,

40
不,不要仅仅因为您可能想删除一个元素而考虑使用双端队列,这确实是一个糟糕的建议。您可能要使用双端队列或向量的原因有很多。的确,从向量中删除元素可能会很昂贵-特别是如果向量较大,则没有理由认为双端队列比刚刚发布的代码示例中的向量要好。
猫头鹰

6
例如,如果您有一个图形应用程序,在其中显示以交互方式插入/删除内容的“列表”,请考虑每秒浏览该列表50-100次以显示它们,然后添加/删除一些内容每分钟次。因此,就总效率而言,将“列表”实现为向量可能是更好的选择。
米歇尔·比洛

Answers:


705

要删除单个元素,您可以执行以下操作:

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

// Deletes the second element (vec[1])
vec.erase(vec.begin() + 1);

或者,一次删除多个元素:

// Deletes the second through third elements (vec[1], vec[2])
vec.erase(vec.begin() + 1, vec.begin() + 3);

50
还要注意二进制operator+不是一定适合其他容器类型的迭代定义的,像list<T>::iterator(你不能做list.begin() + 2对的std::list,你必须使用std::advance该)
bobobobo

您是说“ +1”是第一个元素myVector [0]还是实际位置myVector [1]
Karl Morrison

2
事先,您必须将迭代器保存在变量中。如果使用std :: next,则可以在一行中完成:vec.erase(next(begin(vec),123));
丹妮

8
谢谢所有回答的人。当像删除元素这样的简单操作需要一个进入StackOverflow的类时,我们如何看待类设计?
皮埃尔

5
@Pierre,因为特定元素的数字索引不是访问的主要模型,而是迭代器。查看容器元素的所有函数都使用该容器的迭代器。例如std::find_if
Caleth '18

212

std :: vector上的擦除方法已重载,因此调用起来可能更清晰

vec.erase(vec.begin() + index);

当您只想删除单个元素时。


3
但是,无论您有多少元素,这个问题都会出现。
Zyx 2000年

15
如果只有一个元素,则索引为0,因此可以得到vec.begin()有效的元素。
安妮·奎因

28
我希望有人提到那vec.erase(0)行不通,但vec.erase(vec.begin()+0)(或不带+0)行得通。否则我没有匹配的函数调用,这就是为什么我来这里
qrtLs

@qrtLs vec.erase(0)实际上可以编译,如果0碰巧被解释为空指针常量……
LF

57
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
    std::vector<T>::iterator it = vec.begin();
    std::advance(it, pos);
    vec.erase(it);
}

2
麦克斯(Max),是什么使该功能比以下更好:template <typename T> void remove(std::vector<T>& vec, size_t pos) { vec.erase(vec.begin + pos); }我并不是说任何一个都更好,只是出于个人利益询问并返回此问题可能获得的最佳结果。

13
@JoeyvG:由于a vector<T>::iterator是随机访问的迭代器,因此您的版本很好,也许更清晰。但是,如果您将容器更改为另一个不支持随机访问迭代器的容器,则Max发布的版本应该可以正常工作
Lily Ballard 2012年

2
这是imo更好的答案,因为它也适用于其他容器格式。您也可以使用std :: next()。
2013年

更好的方法,因为它不依赖于容器的内部。
BartoszKP

仅当您认为这不是向量(即列表)时,才需要std :: advance。但是正如您在此处指定的那样,operator +会不会更简单?根据这个stackoverflow.com/questions/1668088/…,操作员+可能会提高性能
尼尔·麦吉尔

14

erase方法将以两种方式使用:

  1. 删除单个元素:

    vector.erase( vector.begin() + 3 ); // Deleting the fourth element
  2. 擦除范围:

    vector.erase( vector.begin() + 3, vector.begin() + 5 ); // Deleting from fourth element to sixth element

7
这是接受答案将近7年之后的重复答案。请不要这样做。
AlastairG

10

实际上,该erase功能适用于两个配置文件:

  • 删除单个元素

    iterator erase (iterator position);
  • 删除一系列元素

    iterator erase (iterator first, iterator last);

由于std :: vec.begin()标记了容器的开始,并且如果我们要删除向量中的ith元素,则可以使用:

vec.erase(vec.begin() + index);

如果仔细看,vec.begin()只是指向向量起始位置的指针,将i的值添加到它的位置会将指针增加到i的位置,因此我们可以通过以下方式访问指向ith元素的指针:

&vec[i]

所以我们可以这样写:

vec.erase(&vec[i]); // To delete the ith element

6
-1最后一行不编译(至少在VS2017中)。该代码假定vector :: iterator可从原始指针隐式构造,这不是标准要求的。
CuriousGeorge

1
这是真正的特别调试迭代器
NISHANT辛格

9

如果您有一个无序向量,则可以利用它无序的事实,并使用我在CPPCON的Dan Higgins身上看到的东西

template< typename TContainer >
static bool EraseFromUnorderedByIndex( TContainer& inContainer, size_t inIndex )
{
    if ( inIndex < inContainer.size() )
    {
        if ( inIndex != inContainer.size() - 1 )
            inContainer[inIndex] = inContainer.back();
        inContainer.pop_back();
        return true;
    }
    return false;
}

由于列表顺序无关紧要,因此只需将列表中的最后一个元素复制到要删除的项目上方,然后弹出并删除最后一个项目即可。


我认为如果向量是无序的,这是最好的答案。它不依赖于iterator + index实际上会在该索引处返回迭代器位置的假设,这并非对所有可迭代容器都是正确的。通过利用后指针,它也是恒定的复杂度,而不是线性的。
theferrit32 '18

1
这完全需要添加到std lib中,unordered_remove并且unordered_remove_if……除非确实如此,但我错过了它,这些天这种情况越来越多了:)
Will Crawford

如果会建议使用移动分配或交换而不是复制分配。
Carsten S

std::remove重新排序的容器中,使得所有要除去的元素是在端部,无须做手工这样如果使用的是C ++ 17
基思

@keith有什么std::remove帮助?cppreference声称,即使在C ++ 17中,所有remove重载都需要谓词,并且没有任何索引。
Paul Du Bois

4

如果您使用大向量(大小> 100,000),并且想要删除很多元素,则建议执行以下操作:

int main(int argc, char** argv) {

    vector <int> vec;
    vector <int> vec2;

    for (int i = 0; i < 20000000; i++){
        vec.push_back(i);}

    for (int i = 0; i < vec.size(); i++)
    {
        if(vec.at(i) %3 != 0)
            vec2.push_back(i);
    }

    vec = vec2;
    cout << vec.size() << endl;
}

该代码将vec中无法除以3的每个数字复制到vec2中。然后将其复制到vec2中。非常快。要处理20,000,000个元素,此算法仅需0.8秒!

我对擦除方法做了同样的事情,这需要很多时间:

Erase-Version (10k elements)  : 0.04 sec
Erase-Version (100k elements) : 0.6  sec
Erase-Version (1000k elements): 56   sec
Erase-Version (10000k elements): ...still calculating (>30 min)

6
这如何回答这个问题?
Regis Portalez '16

4
有趣,但与问题无关!
罗迪

就地算法会不会更快?
user202729

2
那是std :: remove_if(+ erase)
RiaD


3

我建议您阅读此书,因为我相信您正在寻找的东西。https://zh.wikipedia.org/wiki/擦除%E2%80%93remove_idiom

如果您使用例如

 vec.erase(vec.begin() + 1, vec.begin() + 3);

您将擦除vector的第n个元素,但是当擦除第二个元素时,vector的所有其他元素将移动并且vector大小将为-1。如果由于矢量size()减小而遍历矢量,则可能会出现问题。如果您有这样的问题,则提供的链接建议使用标准C ++库中的现有算法。和“删除”或“删除_if”。

希望这对您有所帮助


0

先前的答案假定您始终具有带符号的索引。可悲的是,std::vector用途size_type用于索引和difference_type迭代器算法,因此如果启用了“ -Wconversion”和朋友功能,它们将无法一起使用。这是回答问题的另一种方式,同时能够处理有符号和无符号:

去除:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
void remove(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);
    v.erase(iter);
}

服用:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
T take(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);

    auto val = *iter;
    v.erase(iter);

    return val;
}

0

如果要通过在向量中找到元素的值来删除元素,这是另一种方法,您只需要在向量上执行此操作即可。

vector<int> ar(n);
ar.erase(remove(ar.begin(), ar.end()), (place your value here from vector array));

它会从这里删除您的价值。谢谢


0

这个怎么样?

void squeeze(vector<int> &v)
{
    int j = 0;
    for (int i = 1; i < v.size(); i++)
        if (v[i] != v[j] && ++j != i)
            v[j] = v[i];
    v.resize(j + 1);
}

-4

最快的方法(用于时间为的编程竞赛,complexity()=常数)

可以在1秒内擦除100M项;

    vector<int> it = (vector<int>::iterator) &vec[pos];
    vec.erase(it);

最易读的方式: vec.erase(vec.begin() + pos);


2
这是非常不可移植的。它将与libstdc ++一起使用,但不适用于libc ++,不适用于MSVC。 vector<int>::iterator不一定与int *
Marshall Clow

2
令人作呕,我想我将更改libstdc ++使其停止工作。
乔纳森·韦克利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.