在我们刚开始接触stl标准容器的时候,或者说只是看了某些方法的字面意思就对该方法的作用进行一些猜测,这样其实是有很大缺陷的,比如今天要说的remove方法,看字面意思,我们很容易理解为是从容器中删除什么。
但其实呢?我们先看下remove方法的申明和定义。
template <class _ForwardIter, class _Tp>
_ForwardIter remove(_ForwardIter __first, _ForwardIter __last, const _Tp& __value)
{
__STL_REQUIRES(_ForwardIter, _Mutable_ForwardIterator);
__STL_REQUIRES_BINARY_OP(_OP_EQUAL, bool,
typename iterator_traits<_ForwardIter>::value_type, _Tp);
__STL_CONVERTIBLE(_Tp, typename iterator_traits<_ForwardIter>::value_type);
__first = find(__first, __last, __value);
_ForwardIter __i = __first;
return __first == __last ? __first
: remove_copy(++__i, __last, __first, __value);
}
通过上面的申明我们可以看出,remove跟其他的算法方法一样,参数也是一个迭代器的区间和需要remove的元素的值。同时,我们也能看出来remove方法的参数是没有容器的,所以,它并不知道元素存在哪个容器中。也没办法通过迭代器推导出容器类型。
我们看下下面的例子。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
typedef vector<int> Intvec;
typedef vector<int>::iterator IntIter;
Intvec v;
Intvec r;
for(int index = 0; index < 10; ++index)
{
v.push_back(index + 1);
}
v[3] = v[5] = v[9] = 100;
cout << v.size() << endl; // 10
remove(v.begin(), v.end(), 100);
cout << v.size() << endl; // 10
return 0;
}
通过上面的例子我们能够看出,在调用remove方法前后,容器v的size并没有改变,这也就是说,remove方法并不能实际性的从容器中删除元素。主要是因为上面所说的,remove并不能得知元素所存的容器。也就不能调用容器的成员函数来进行元素的删除。
那么既然不能删除,如果我们将容器 v 中的元素数据打印出来看下究竟是什么。
for(IntIter i = v.begin(); i != v.end(); ++i)
{
cout << *i << " "; // 1 2 3 5 7 8 9 8 9 100
}
remove的实际作用是在我们设置的区间内对元素进行了移动,将不被删除的元素移到容器的前面,并且能够保持原有容器的顺序。
其返回值是指向最后一个不被删除元素的迭代器。所以我们看下下面的例子。
IntIter nend = remove(v.begin(), v.end(), 100);
for(IntIter i = v.begin(); i != nend; ++i)
{
cout << *i << " "; // 1 2 3 5 7 8 9
}
cout << endl;
for(IntIter i = nend; i != v.end(); ++i)
{
cout << *i << " "; // 8 9 100
}
不知道你有没有发现问题,按照我们上面的推导,说remove方法是将容器中不被删除的元素移动到容器的头部,并能保持原有元素的顺序,返回指向最后一个不被删除的元素的迭代器,那么我们是不是可以合情合理的理解为,在 nend 和 v.end()
迭代器之间保存的就是我们需要被删除的元素呢?
结果却跟目前的是不一样的。为什么呢?
remove函数的操作步骤是这样的:
- 循环遍历矢量 v,如果发现某个下标(index)对应的元素需要被删除,因为,他会记下来该下标,然后遍历下一个(index + 1);
- 如果下一个(index + 1)不需要被删除,则会将两个的值互换,并记下下一个(index + 1)是需要下次被互换的元素,如果需要删除则直接跳过;
- 以此类推,直到容器末尾;
- 返回最后一个不被删除的迭代器。
上面的步骤用我们简单的理解也就是两个游标的问题。
我们也就能够理解上面的操作例子为什么会是 // 1 2 3 5 7 8 9 8 9 100 这样了。
经过remove
之后,我们需要删除的元素全都在容器的新的容器尾部和实际的尾部这个区间内,如果需要删除元素,则直接调用erase
就可以了。
v.erase(remove(v.begin(), v.end(), 100), v.end());
for(IntIter i = v.begin(); i != v.end(); ++i)
{
cout << *i << " "; // 1 2 3 5 7 8 9
}
但是对于list容器是有一些特殊的。因为list容器的remove函数是直接进行了元素的删除。
typedef list<int> IntList;
for(int index = 0; index < 10; ++index)
{
v.push_back(index + 1);
}
v.insert(++v.begin(), 100);
v.insert(v.end(), 100);
for(ListIter i = v.begin(); i != v.end(); ++i)
{
cout << *i << " "; // 1 100 2 3 4 5 6 7 8 9 10 100
}
v.remove(100);
cout << v.size() << endl; // 10
for(ListIter i = v.begin(); i != v.end(); ++i)
{
cout << *i << " "; // 1 2 3 4 5 6 7 8 9 10
}
通过上面的例子我们能可以看出,remove是list容器的成员函数,而在其他容器中则没有名为remove的成员函数。
我们要接受我们这并不完美的世界,因为这正是我们所拥有的。
下一节,我们看一看能不能用 erase-remove来删除保存了指针的容器元素呢。