【问题标题】:How do I erase elements from STL containers?如何从 STL 容器中擦除元素?
【发布时间】:2025-11-28 10:35:02
【问题描述】:

如何从 STL 容器中删除具有指定 或满足某些 条件的元素?

对于不同种类的容器,是否有一种通用或统一的方法?

【问题讨论】:

  • 可能对 C++ 常见问题有好处。
  • @LuchianGrigore 这通常是如何工作的?这通常是由社区根据评论中的讨论决定的,还是每个人都可以决定标记这个c++-faq(嗯,当然可以,但也许这被认为是不好的礼仪,因为这个标记是更严格的内容受控)。或者应该只是标记它并让社区有责任在他们不同意的情况下删除标记。
  • @ChristianRau 通常在the lounge 中讨论。

标签: c++ c++11 stl std


【解决方案1】:

不幸的是,没有一个单一的uniform 接口或模式可用于从 STL 容器中擦除元素。 但出现了三种行为:

std::vector 模式

要从 std::vector 中删除满足特定条件的元素,一种常见的技术是所谓的erase-remove idiom

如果vstd::vector 的一个实例,并且我们想从向量中删除值为x 的元素,则可以使用如下代码:

// Erase elements having value "x" from vector "v"
v.erase( std::remove(v.begin(), v.end(), x), v.end() );

如果擦除元素要满足的条件比简单的“要擦除的元素== x”更复杂,则可以使用std::remove_if()算法代替std::remove()

// Erase elements matching "erasing_condition" from vector "v"
v.erase( std::remove_if(v.begin(), v.end(), erasing_condition), v.end() );

其中erasing_condition 是一元谓词,可以用多种形式表示:例如它可以是一个bool-returning函数,以向量元素类型为输入(因此如果返回值为true,则该元素将从向量中删除;如果是false,则惯于);或者它可以in-line表示为一个lambda;可以是functor;等等

std::remove()std::remove_if() 都是来自 <algorithm> 标头的通用算法。)

这里有明确的解释from Wikipedia

algorithm 库提供了 removeremove_if 为此的算法。因为这些算法在一系列 由两个前向迭代器表示的元素,它们不知道 底层容器或集合。因此,实际上没有元素 从容器中取出。相反,所有不适合的元素 删除条件被放在范围的前面,在 相同的相对顺序。其余元素保留为有效,但 未指定的状态。完成后,remove 返回一个迭代器 指向最后一个未删除的元素。

为了真正从容器中消除元素,remove 被组合在一起 使用容器的erase 成员函数,因此得名 “擦除删除习语”。

基本上,std::remove()std::remove_if() 将满足 擦除条件的元素压缩到范围的前面(即vector 的开头),然后@ 987654349@ 实际上是从容器中消除了剩余的元素。

这种模式也适用于其他容器,例如 std::deque

std::list 模式

要从 std::list 中删除元素,可以使用简单的 remove()remove_if() 方法

// Erase elements having value "x" from list "l"
l.remove( x )

// Erase elements satisfying "erasing_condition" from list "l"
l.remove_if( erasing_condition );

(其中erasing_condition 是一元谓词,与上一节中讨论的std::remove_if() 具有相同的特征。)

相同的模式可以应用于类似的容器,例如 std::forward_list

关联容器(例如 std::map、std::set、...)模式

关联容器,例如 std::mapstd::setstd::unordered_map 等。此处描述的常见模式:

  1. 如果擦除条件是简单的键匹配(即”擦除元素 有键 x"),然后可以调用一个简单的 erase() 方法

    // Erase element having key "k" from map "m":
    m.erase( k );
    
  2. 如果擦除条件比较复杂,用一些习惯来表示 一元谓词(例如“擦除所有奇数元素”),然后可以使用for 循环 (在循环体中检查明确的擦除条件,并调用erase(iterator) 方法):

    //
    // Erase all elements from associative container "c", satisfying "erasing_condition":
    //
    for (auto it = c.begin(); it != c.end(); /* "it" updated inside loop body */ )
    {
        if ( erasing_condition(*it) )
        {   
            // Erase the element matching the specified condition 
            // from the associative container.
            it = c.erase(it);
    
            // Note:
            // erase() returns an iterator to the element 
            // that follows the last element removed, 
            // so we can continue the "for" loop iteration from that position.
        }
        else
        {
            // Current element does _not_ satisfy erasing condition,
            // so we can just move on to the next element.
            ++it;
        }       
    }     
    

需要统一的方法

从上述分析中可以看出,遗憾的是,从 STL 容器中擦除元素并没有统一的通用方法。

下表总结了上述模式:

----------------+------------------------------------------             
   Container    |            Erasing Pattern
----------------+------------------------------------------                
                |
 vector         |    Use erase-remove idiom.
 deque          |
                |
----------------+------------------------------------------               
                |
 list           |    Call remove()/remove_if() methods.
 forward_list   |
                |
----------------+------------------------------------------  
                |
 map            |    Simple erase(key) method call, 
 set            |    or 
 unordered_map  |    loop through the container,
 multimap       |    and call erase(iterator) on matching
                |    condition.
 ...            |
                |
----------------+------------------------------------------

根据特定容器编写不同的特定代码容易出错、难以维护、难以阅读等。

但是,可以为不同的容器类型编写具有通用名称(例如 erase()erase_if()重载的函数模板,并将上述模式实现嵌入到这些函数中。
因此,客户端可以简单地调用那些erase()erase_if() 泛型函数,编译器会根据容器类型将调用分派给正确的实现(在编译时)。

by Stephan T. Lavavej here 提出了一种更优雅的方法,使用模板元编程技术。

【讨论】:

  • 当我们有模板约束时,写一个重载的erase会容易得多。
  • 请注意,此答案仅描述按值(或按值的属性)擦除。所有序列和关联容器(array 除外)都有一个统一的接口,用于按迭代器或迭代器范围进行擦除。
  • @MikeSeymour:是的,我专注于按值或值的属性擦除(这在代码中很常见)。我编辑了问题文本以使其更清晰。谢谢。
  • 很好的答案,只是有点不准确。您提供的 std::map::erase 不搜索整个值(键值对),而只搜索一个键(对于地图来说,它不是整个值)。虽然这确实是常见的用例,但在答案中进行一些澄清可能是个好主意。
  • 对于那些担心性能的人,removeremove_if 成语是通过移位(使用移动分配)排序的。要从 std::vector 中删除多个项目,它比迭代和删除每个项目要快。
最近更新 更多