32.erase_remove惯用法

remove无法删除元素

 
remove的声明如下:

1
2
template<class ForwardIterator, class T>
ForwardIterator remove(ForwardIterator first, ForwardIterator last,const T& value);

显然,remove算法操作迭代器而非容器,就像别的算法一样.
如果我们想从容器中去除某个元素,必然需要使用容器的成员函数erase,因为remove无法知道它正在操作的容器———因此有结论,从一个容器中remove元素不会改变元素个数。
1
2
3
4
5
6
7
8
9
vector<int> v;
v.reserve(10);
for (int i = 1; i <= 10; ++i) {
v.push_back(i);
}
cout << v.size();
v[3] = v[5] = v[9] = 99;
remove(v.begin(), v.end(), 99);
cout << v.size();//仍然是10

我们需要了解并记住的是:remove并不“真的”删除东西,因为它做不到。


remove的具体实现

 
remove移动指定区间中的元素,直到所有不需要“删除”的元素在区间开头,并且返回区间的“新逻辑终点”。一图胜千言:

  1. 调用remove之前
    image_1cb48ijia793d6a1smfgv539j9.png-16.5kB
  2. 执行remove
    1
    vector<int>::iterator newEnd(remove(v.begin(), v.end(), 99));
    image_1cb48kk5jh151qlf2iv1br2sb4m.png-19.1kB

然而,因为remove本质上并不是在改变区间元素顺序,所以处仍然存在值,一般而言,真正的remove之后结果如下所示:
image_1cb4a9i721jea2001a4t1agn13i413.png-18.8kB
这是由于remove的实现所决定的,你可以想象remove完成了一种压缩去,被删除的值表演了被填充的角色,具体而言,其执行方略如下:

  1. remove检测v[0],发现无需删除,然后向后移动
  2. v[3]应该删除,于是记录v[3]需要被覆盖,移动到v[4]
  3. v[4]无需删除,将v[4]赋予v[3],记录v[4]应该被覆盖,移动到v[5]
  4. v[5]应该被删除,所以忽略并移动到6,此时记得4、5都应该被填充
  5. 诸如此类,移动过程如下图所示

image_1cb4ah4ep164kjup1jpns06hg31g.png-13.2kB


erase_remove惯用法

 
erase与remove是一对好搭档,我们应该很自然的配合使用:

1
2
vector<int> v;
v.erase(remove(v.begin(), v.end(), 99),v.end());

list是一个意外,它的remove综合了remove与erase的功能.
除此之外,remove_if与unique行为类似于remove,所以记得它们也应该配合erase使用。