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