高效擦除/移除(Erase–remove idiom)std::vector元素

首先,我们使用整数来填充一个std::vector实例,之后剔除一些特定元素。我们演示的从vector实例中删除元素正确的方法。

  1. 包含文件是首要任务。
#include <iostream>
#include <vector>
#include <algorithm>
  1. 声明我们所要使用的命名空间。
using namespace std;
  1. 现在我们来创建一个vector实例,并用整数填满它。
int main(){
    vector<int> v{1, 2, 3, 2, 5, 2, 6, 2, 4, 8};
  1. 然后移除一些元素。需要我们移除哪些呢?2出现的太多次了,就选择2吧。让我们移除它们吧。
    const auto new_end(remove(begin(v), end(v), 2)); 
  1. 已经完成了两步中的一步。vector在删除这些元素之后,长度并没有发生变化。那么下一步就让这个vector变得短一些。
    v.erase(new_end, end(v));
  1. 我们在这里暂停一下,输出一下当前vector实例中所包含的元素。
    for(auto i : v){
        cout << i << ", "; 
    }
    cout << '\n';
  1. 现在,让我们来移除一组指定的数据。为了完成这项工作,我们先定义了一个谓词函数,其可接受一个数作为参数,当这个数是奇数时,返回true。
    const auto odd([](int i){return i % 2 != 0;});
  1. 这里我们使用remove_if函数,使用上面定义的谓词函数,来删除特定的元素。这里我们将上面删除元素的步骤合二为一。
    v.erase(remove_if(begin(v), end(v), odd), end(v));
  1. 所有的奇数都被删除,不过vector实例的容量依旧是10。最后一步中,我们将其容量修改为正确的大小。需要注意的是,这个操作会让vector重新分配一段内存,以匹配相应元素长度,vector中已存的元素会移动到新的内存块中。
    v.shrink_to_fit();
  1. 打印一下现在vector实例中的元素。
    for (auto i : v) {
        cout << i << ", ";
    }
    cout << '\n';
}
  1. 编译完成后,运行程序,就可以了看到两次删除元素后vector实例中所存在的元素。
$ ./main
1, 3, 5, 6, 4, 8,
6, 4, 8,

vector中移除2的代码如下所示:

const auto new_end (remove(begin(v), end(v), 2));
v.erase(new_end, end(v));

std::beginstd::end函数都以一个vector实例作为参数,并且返回其迭代器,迭代器分别指向第一个元素和最后一个元素,就如下图所示。

在这里插入图片描述

每次移除一个元素, new_end向前移动一下
在这里插入图片描述

std::remove只是将要删除的元素移动到容器末尾,而不是将其真正删除,所以这个函数也可以用于不支持空间大小变化的数据类型
std::remove在删除2的时候,会先将非2元素进行移动,然后修改end迭代器的指向。该算法将严格保留所有非2个值的顺序

注解
调用 std::remove 常后随容器的 erase 成员函数,它擦除未指定值并减小容器的物理大小以匹配其新的逻辑大小。这两个调用一并被称为擦除-移除手法,它可通过对所有标准序列容器重载的 std::erase 自由函数,或对所有标准容器重载的 std::erase_if 自由函数达成 (C++20 起)。

同名的容器成员函数 list::remove、 list::remove_if、 forward_list::remove 及 forward_list::remove_if 擦除被移除的元素。

这些算法通常不能用于如 std::set 与 std::map 的关联容器,因为其迭代器类型不解引用为可移动赋值 (MoveAssignable) 类型(这些容器中的键不可修改)。

标准库亦于 <cstdio> 定义 std::remove 的重载,它接收 const char* 的重载并用于删除文件。

因为 std::remove 以引用接收 value ,若引用到范围 [first, last) 中的元素,则它可能有不可预期的行为。

在2步中,2的值仍然存在,并且vector应该变短。并且4和8在现有的vector中重复了。这是怎么回事?

让我们再来看一下所有的元素,目前vector的范围并不是原来那样了,其是从begin迭代器,到new_end迭代器。new_end之后的值其实就不属于vector实例了。我们会注意到,在这个范围内的数值,就是我们想要的正确结果,也就是所有的2都被移除了。

最后,也就是为什么要调用erase函数:我们需要告诉vector实例,new_endend之间的元素我们不需要了。我们仅需要保留beginnew_end间的元素就好了。erase函数会将end指向new_end。这里需要注意的是std::remove会直接返回new_end迭代器,所以我们可以直接使用它。

Note:

vector在这里不仅仅移动了内部指针。如果vector中元素比较复杂,那么在移除的时候,会使用其析构函数来销毁相应的对象。

最后,这个向量就如步骤3所示:的确变短了。那些旧的元素已经不在vector的访问范围内了,不过其仍存储在内存中。

为了不让vector浪费太多的内存,我们在最后调用了shrink_to_fit。该函数会为元素分配足够的空间,将剩余的元素移到该空间内,并且删除之前那个比较大的内存空间。

在上面的第8步中,我们定义了一个谓词函数,并在std::remove_if中使用了它。因为不论删除函数返回怎么样的迭代器,在对vector实例使用擦除函数都是安全的。如果vector中全是偶数,那么std::remove_if不会做任何事情,并且返回end迭代器。之后的调用就为v.erase(end, end); ,同样没有做任何事情。

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> v {1, 2, 3, 2, 5, 2, 6, 2, 4, 8};

    {
        const auto new_end (remove(begin(v), end(v), 2));
        for (auto i : v) {
        cout << i << ", ";
        }
        cout << '\n';
        
        v.erase(new_end, end(v));
    }

    for (auto i : v) {
        cout << i << ", ";
    }
    cout << '\n';

    {
        const auto odd ([](int i) { return i % 2 != 0; });
        const auto new_end (remove_if(begin(v), end(v), odd));
        v.erase(new_end, end(v));
    }

    v.shrink_to_fit();

    for (auto i : v) {
        cout << i << ", ";
    }
    cout << '\n';
}

输出
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值