STL中的两类元素删除操作
主要参考侯捷老师的《STL源码剖析》。
通常在STL中的删除操作有以下两种:
- erase() 函数;
- remove() 函数;
在介绍这两个函数前还需要介绍以下全局函数copy()和destory():
copy()函数经常被调用,为此,SGI STL的copy算法用尽各种办法包括函数重载,型别特性,偏特化等技巧来加强效率。copy()的函数原型如下:
template<class II, class OI>
inline OI copy(II first, II last, OI result);
将区间 [first, last) 中的元素全部拷贝到区间 [result, result + last - first),特别地,如果 result 在区间 [first, last) 中,则行为未定义。
而destory()函数对单个迭代器的操作如下:
template<class T>
inline void destory(T *pointer) {
pointer->~T();
}
而对于一组迭代器 [first, last),若该类型有trivial destructor,则对区间[first, last)依次调用 destory(T *pointer) 也即调用析构函数。
- erase() 函数
1.在vector中,erase() 函数定义如下:
其中finish是指向空间的尾部,copy()和destory()是全局函数
iterator erase(iterator position) {
copy(position + 1, finish, position);
--finish;
destory(finish);
return position;
}
iterator erase(iterator first, iterator last) {
iterator i = copy(last, finish, first);
destory(i, finish);
finish = finish - (last - first);
return first;
}
2.在list中,erase() 函数原型如下:
iterator erase( iterator pos );
iterator erase( iterator first, iterator last );
这里手动使用链表的方式删除。
3. 在set中,erase() 函数原型如下:
void erase( iterator pos );
void erase( iterator first, iterator last );
size_type erase( const key_type& key );
这里是调用底层RBtree对象的erase()方法。
现在我们大概了解了erase()方法,如果在循环中用erase()方法来删除元素,千万要注意:
如果容器是标准序列容器,写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你的迭代器。
如果容器是标准关联容器,写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它(因为关联容器的erase方法通常返回void,所以无法用它的返回值更新迭代器)。
看下面的例子:
#include <bits/stdc++.h>
using namespace std;
template<typename T>
struct display {
void operator() (const T& x) const {
cout << x << ' ';
}
};
int main()
{
int ia[11] = {0,1,2,3,4,5,6,6,6,7,8};
vector<int> iv(ia, ia+sizeof(ia)/sizeof(int));
for(auto ite = iv.begin(); ite != iv.end(); ++ite) {
if(*ite != 6)
iv.erase(ite);
}
for_each(iv.begin(), iv.end(), display<int>());
cout << endl; //1 3 5 6 6 6 8
return 0;
}
在循环中使用迭代器删除元素6,如果直接用迭代器删除,指向那个元素的所有迭代器就失效了,然后再执行++也就成了未定义的行为,使用的编译器是g++,编译器应该是对语句iv.erase(ite) 做了优化 ite= iv.erase(ite)?不太清楚,从运行的结果来看是这样的。正确的做法应该把循环改成以下:
for(auto ite = iv.begin(); ite != iv.end();) {
if(*ite != 6)
ite = iv.erase(ite);
else ++ite;
}
如果是标准关联容器,循环改成以下:
for(auto ite = iv.begin(); ite != iv.end();) {
if(*ite != 6)
iv.erase(ite++);
else ++ite;
}
remove()函数
1.STL全局函数: 函数原型如下:
//remove
template<class FI, class T>
FI remove(FI first, FI last, const T& value) {
first = find(first, last, value);
FI next = first;
return first == last ? first :
remove_copy(++next, last, first, value);
}
//remove_copy
template<class II, class OI, class T>
OI remove_copy(II first, II last, OI result, const T& value) {
for(; first != last; ++first) {
if(*first != value) {
*result = *first;
++first;
}
}
return result;
}
remove_if()函数原型如下:
//remove_if
template<class FI, class PD>
FI remove_if(FI first, FI last, PD pred) {
first = find_if(first, last, pred);
FI next = first;
return first == last ? first :
remove_copy_if(++next, last, first, pred);
}
//remove_copy_if
template<class II, class OI, class PD>
OI remove_copy_if(II first, II last, OI result, PD pred) {
for(; first != last; ++first) {
if(!pred(*first)){
*result = *first;
++result;
}
}
return result;
}
//find_if
template<class II, class PD>
II my_find_if(II first, II last, PD pred) {
while(first != last && !pred(*first))
++first;
return first;
}
从上述函数原型可以知道,remove 或者remove_if “删除”了对应的元素,其本质是:每删除一个元素,后续的元素都往前移,但是整个区间的元素的数量并没有发生改变,因此区间的末尾可能存在垃圾元素。
看下面的例子:
#include <bits/stdc++.h>
using namespace std;
template<typename T>
struct display {
void operator() (const T& x) const {
cout << x << ' ';
}
};
int main()
{
int ia[11] = {0,1,2,3,4,5,6,6,6,7,8};
vector<int> iv(ia, ia+sizeof(ia)/sizeof(int));
auto ite = remove(iv.begin(), iv.end(), 6);
cout << endl; //0 1 2 3 4 5 7 8 6 7 8
iv.erase(ite, iv.end());
for_each(iv.begin(), iv.end(), display<int>());
cout << endl; //0 1 2 3 4 5 7 8
return 0;
}
输出结果如下:
remove(iv.begin(), iv.end(), 6) 这句是删除iv 中的所有6,第一次输出的为0 1 2 3 4 5 7 8 6 7 8,其中最后6 7 8是垃圾值,我们采用iv的erase()方法删除:
iv.erase(ite, iv.end()) 因此第二次会输出0 1 2 3 4 5 7 8。这也就是《effective STL》中的条款32:“如果你真的想删除东西的话就在类似remove的算法后接上
erase”。
2.remove()除了是STL的全局函数,在链表list中也有一个remove()成员函数:
其中begin() 返回链表首节点, end() 返回末节点。
template<class T, class Alloc>
void list<T, Alloc>::remove(const T& value) {
iterator first = begin(), last = end();
while(first != last) {
iterator next = first;
++next;
if(*first == value) erase(first);
first = next;
}
}
这里使用了迭代器删除元素,上面我们就学习了,不能直接在循环里使用迭代器删除元素,remove()方法也向我们展示了这一点。