STL中的两类元素删除操作

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()方法也向我们展示了这一点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值