【问题标题】:How do I erase an element from std::vector<> by index?如何按索引从 std::vector<> 中删除元素?
【发布时间】:2010-10-26 21:10:33
【问题描述】:

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

std::vector<int> vec;

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

vec.erase(???);

【问题讨论】:

  • 考虑使用在两端提供插入和删除的 std::deque。
  • 不,不要仅仅因为你可能想删除一个元素就考虑使用 deque,这真的是一个糟糕的建议。您可能想要使用双端队列或向量的原因有很多。确实,从向量中删除元素的成本很高 - 特别是如果向量很大,但没有理由认为双端队列会比您刚刚发布的代码示例中的向量更好。
  • 例如,如果您有一个图形应用程序,您可以在其中显示以交互方式插入/删除事物的“列表”,考虑您每秒运行列表 50-100 次以显示它们,你每分钟添加/删除几次。因此,就总效率而言,将“列表”实现为向量可能是更好的选择。
  • 我推荐 std::vector.erase(...),这也是我的偏好——你可以选择删除单个元素或范围。

标签: c++ stl vector erase


【解决方案1】:

要删除单个元素,您可以这样做:

std::vector<int> vec;

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

// Deletes the second element (vec[1])
vec.erase(std::next(vec.begin()));

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

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

【讨论】:

  • 注意二进制operator+不一定必须为其他容器类型上的迭代器定义,例如list&lt;T&gt;::iterator(你不能在std::list上做list.begin() + 2,你必须使用std::advance
  • 提前你必须将迭代器保存在一个变量中。如果你使用 std::next 你可以在一行中完成: vec.erase( next(begin(vec), 123) );
  • 感谢所有回答的人。当像删除元素这样简单的操作需要一个人来到 StackOverflow 时,我们如何看待类设计?
  • @Pierre 因为特定元素的 数字索引 不是主要的访问模型,iterator 才是。查看容器元素的所有函数都使用该容器的迭代器。例如。 std::find_if
  • @Caleth 是的,但是 std::vector 仍然可以为这个非常常见的用例提供一种方法。每个人都在抨击 Qt 容器,但 QList 例如有 removeOne() 与 std::vector 的丑陋相比,这只是一个简单的问题。
【解决方案2】:

std::vector 上的擦除方法被重载,所以调用起来可能更清晰

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

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

【讨论】:

  • 但是不管你有多少元素都会出现这个问题。
  • 如果只有一个元素,则索引为0,所以你得到vec.begin(),这是有效的。
  • 我希望有人提到vec.erase(0) 不起作用,但vec.erase(vec.begin()+0)(或没有+0)可以。否则我得不到匹配的函数调用,这就是我来这里的原因
  • @qrtLs vec.erase(0) 如果 0 恰好被解释为空指针常量,则实际上可能会编译 ...
  • @qrtLs erase() 函数将迭代器类型作为参数;因为 0 不是一个迭代器,它会给编译器错误,因为没有匹配的函数调用。
【解决方案3】:
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);
}

【讨论】:

  • Max,是什么让这个功能比:template &lt;typename T&gt; void remove(std::vector&lt;T&gt;&amp; vec, size_t pos) { vec.erase(vec.begin + pos); } 更好,只是出于个人兴趣并返回这个问题可能得到的最佳结果。
  • @JoeyvG:因为vector&lt;T&gt;::iterator 是一个随机访问迭代器,所以您的版本很好,可能更清晰一些。但是,如果您将容器更改为另一个不支持随机访问迭代器的容器,Max 发布的版本应该可以正常工作
  • 这是 imo 更好的答案,因为它也适用于其他容器格式。你也可以使用 std::next()。
  • 更好的方法,因为它不依赖于容器的内部。
  • std::advance 仅在您认为这不是向量(即列表)时才需要。但是正如您在此处指定的那样, operator+ 不会更简单吗?根据这个stackoverflow.com/questions/1668088/…,operator+ 可能会提高性能
【解决方案4】:

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 年后的重复答案。请不要这样做。
  • @AlastairG 这个答案比原始答案更短更清晰,尽管它在技术上可能只是一个编辑(虽然这样的编辑可能违背原始答案的 OP 的意愿)跨度>
  • 我认为我们应该添加 - 它从第 4 到第 6 个元素中删除(第 6 个元素不包括/删除)
【解决方案5】:

用索引擦除一个元素:

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

用值擦除元素:

vec.erase(find(vec.begin(),vec.end(),value));

【讨论】:

  • 与其他现有的较旧和赞成的答案相比,请更清楚地说明此答案提供的额外见解。
【解决方案6】:

实际上,erase 函数适用于两个配置文件:

  • 删除单个元素

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

    iterator erase (iterator first, iterator last);
    

由于 std::vec.begin() 标志着容器的开始,如果我们想删除向量中的第 i 个元素,我们可以使用:

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

如果仔细观察,vec.begin() 只是一个指向向量起始位置的指针,将 i 的值添加到它会增加指向 i 位置的指针,因此我们可以访问指向第 i 个元素的指针作者:

&vec[i]

所以我们可以这样写:

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

【讨论】:

  • -1 最后一行没有编译(至少在 VS2017 中)。代码假定 vector::iterator 可以从原始指针隐式构造,这不是标准要求的。
  • 对于调试迭代器尤其如此
【解决方案7】:

如果你有一个无序向量,你可以利用它是无序的这一事实,并使用我在 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 将实际返回该索引处的迭代器位置的假设,这对于所有可迭代容器而言并非如此。通过利用反向指针,它也是恒定的复杂性而不是线性的。
  • 这完全需要以unordered_removeunordered_remove_if 的形式添加到标准库中……除非它已经被添加到我错过了,这几天越来越频繁地发生了:)
  • 如果建议使用移动分配或交换而不是复制分配。
  • std::remove 对容器重新排序,以便所有要删除的元素都在最后,如果您使用的是 C++ 17,则无需像这样手动执行。
  • @keith std::remove 有什么帮助? cppreference 声称即使在 C++17 中,所有 remove 重载都需要谓词,并且没有一个需要索引。
【解决方案8】:

这对某些人来说似乎很明显,但要详细说明上述答案:

如果您在整个向量的循环中使用erase 删除std::vector 元素,您应该以reverse order 处理您的向量,也就是说使用

for (int i = v.size() - 1; i &gt;= 0; i--)

代替(古典)

for (int i = 0; i &lt; v.size(); i++)

原因是索引受到erase 的影响,所以如果你删除第 4 个元素,那么之前的第 5 个元素现在是新的第 4 个元素,它不会被你的循环处理如果你正在做i++

下面是一个简单的例子来说明这一点,我想删除一个 int 向量的所有赔率元素;

#include <iostream>
#include <vector>

using namespace std;

void printVector(const vector<int> &v)
{
    for (size_t i = 0; i < v.size(); i++)
    {
        cout << v[i] << " ";
    }
    cout << endl;
}

int main()
{    
    vector<int> v1, v2;
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
        v2.push_back(i);
    }

    // print v1
    cout << "v1: " << endl;
    printVector(v1);
    
    cout << endl;
    
    // print v2
    cout << "v2: " << endl;
    printVector(v2);
    
    // Erase all odd elements
    cout << "--- Erase odd elements ---" << endl;
    
    // loop with decreasing indices
    cout << "Process v2 with decreasing indices: " << endl;
    for (int i = v2.size() - 1; i >= 0; i--)
    {
        if (v2[i] % 2 != 0)
        {
            cout << "# ";
            v2.erase(v2.begin() + i);
        }
        else
        {
            cout << v2[i] << " ";
        }
    }
    cout << endl;
    cout << endl;
    
    // loop with increasing indices
    cout << "Process v1 with increasing indices: " << endl;
    for (int i = 0; i < v1.size(); i++)
    {
        if (v1[i] % 2 != 0)
        {
            cout << "# ";
            v1.erase(v1.begin() + i);
        }
        else
        {
            cout << v1[i] << " ";
        }
    }
    
    
    return 0;
}

输出:

v1:
0 1 2 3 4 5 6 7 8 9

v2:
0 1 2 3 4 5 6 7 8 9
--- Erase odd elements ---
Process v2 with decreasing indices:
# 8 # 6 # 4 # 2 # 0

Process v1 with increasing indices:
0 # # # # #

请注意,在索引增加的第二个版本中,偶数不会显示,因为它们会因为i++而被跳过

【讨论】:

    【解决方案9】:

    如果您使用大型向量(大小 > 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 复制到 vec 中。它非常快。要处理 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)
    

    【讨论】:

    • 这如何回答这个问题?
    • 有趣,但与问题无关!
    • 就地算法不会更快吗?
    • 那是 std::remove_if (+erase)
    【解决方案10】:

    要删除元素,请使用以下方式:

    // declaring and assigning array1 
    std:vector<int> array1 {0,2,3,4};
    
    // erasing the value in the array
    array1.erase(array1.begin()+n);
    

    如需更广泛的概述,您可以访问:http://www.cplusplus.com/reference/vector/vector/erase/

    【讨论】:

    【解决方案11】:

    我建议阅读这篇文章,因为我相信这就是你要找的东西。https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom

    如果你使用例如

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

    您将擦除向量的第 n 个元素,但是当您擦除第二个元素时,向量的所有其他元素将被移动并且向量大小将为 -1。如果您循环遍历向量,这可能会出现问题,因为向量 size() 正在减小。如果您遇到此类问题,建议您使用标准 C++ 库中的现有算法。和“删除”或“remove_if”。

    希望这会有所帮助

    【讨论】:

      【解决方案12】:

      如果您需要擦除 for 循环内的元素,请执行以下操作:

      for(int i = 0; i < vec.size(); i++){
      
          if(condition)
              vec.erase(vec.begin() + i);
      
      }
      

      【讨论】:

        【解决方案13】:

        前面的答案假设您总是有一个带符号的索引。可悲的是,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;
        }
        

        【讨论】:

          【解决方案14】:

          如果你想通过在向量中找到它的值来删除一个元素,这里还有另一种方法,你只需要在向量上执行此操作。

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

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

          【讨论】:

            【解决方案15】:

            这个怎么样?

            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);
            }
            

            【讨论】:

              【解决方案16】:

              最快的方式(时间复杂度()=常数的编程竞赛)

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

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

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

              【讨论】:

              • 这是非常不便携的;它适用于 libstdc++,但不适用于 libc++,也不适用于 MSVC。 vector&lt;int&gt;::iterator 不一定与int * 相同
              • 太恶心了,我想我会改变 libstdc++ 让它停止工作。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-09-30
              • 2011-11-13
              • 2016-03-30
              • 1970-01-01
              • 1970-01-01
              • 2013-02-28
              相关资源
              最近更新 更多