zoukankan      html  css  js  c++  java
  • 慎重选择删除元素的方法

    摘自《Effective STL》第9条

    • 对于连续内存的容器(vector、deque 或 string),那么最好的办法是使用 erase-remove 的习惯用法:
     1 #include <iostream>
     2 #include <memory>
     3 #include <vector>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 int main()
     9 {
    10     vector<int> vec = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9};
    11 
    12     for (auto i : vec) {
    13         cout << i << " ";
    14     }
    15     cout << endl;
    16 
    17     vec.erase(remove(vec.begin(), vec.end(), 2), vec.end());
    18 
    19     for (auto i : vec) {
    20         cout << i << " ";
    21     }
    22     cout << endl;
    23 }
    • 对于 list,使用 list 的成员函数 remove 更加有效
     1 #include <iostream>
     2 #include <memory>
     3 #include <list>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 int main()
     9 {
    10     list<int> c = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9};
    11 
    12     for (auto i : c) {
    13         cout << i << " ";
    14     }
    15     cout << endl;
    16 
    17     c.remove(2);
    18 
    19     for (auto i : c) {
    20         cout << i << " ";
    21     }
    22     cout << endl;
    23 }
    • 对于关联容器(set、multiset、map、multimap),这些容器没有 remove 成员函数。使用 remove 算法可能会覆盖容器的值,同时可能会破坏容器。
     1 #include <iostream>
     2 #include <memory>
     3 #include <map>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 int main()
     9 {
    10     multimap<int, int> c = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9},{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9}};
    11 
    12     for (auto i : c) {
    13         cout << i.second << " ";
    14     }
    15     cout << endl;
    16 
    17     c.erase(2);
    18 
    19     for (auto i : c) {
    20         cout << i.second << " ";
    21     }
    22     cout << endl;
    23 }

    这样做不仅正确,而且高效,只需要对数时间开销。关联容器的 erase 成员函数还有另外一个优点,即它是基于等价(equivalence)而不是相等(equality)。

    如果想使用判别式函数, 怎么做?

    • 对于序列容器(vector、string、deque、list),把 remove 的调用换成调用 remove_if 即可
     1 #include <iostream>
     2 #include <memory>
     3 #include <vector>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 class Foo
     9 {
    10 public:
    11     Foo(int x):_x(x) {}
    12     ~Foo() {}
    13     void print() { cout << _x; }
    14     int getX() { return _x; }
    15 private:
    16     int _x;
    17 };
    18 
    19 bool badValue(Foo& f)
    20 {
    21     if (f.getX() == 2) {
    22         return true;
    23     }
    24     return false;
    25 }
    26 
    27 int main()
    28 {
    29         vector<Foo> c;
    30 
    31     for (int i = 0; i < 10; ++i) {
    32         Foo f(i);
    33         c.push_back(f);
    34     }
    35 
    36     for (int i = 0; i < 10; ++i) {
    37         Foo f(i);
    38         c.push_back(f);
    39     }
    40 
    41     for (auto i : c) {
    42         i.print();
    43         cout << " ";
    44     }
    45     cout << endl;
    46 
    47     c.erase(remove_if(c.begin(), c.end(), badValue), c.end());
    48 
    49     for (auto i : c) {
    50         i.print();
    51         cout << " ";
    52     }
    53     cout << endl;
    54 }
     1 #include <iostream>
     2 #include <memory>
     3 #include <list>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 class Foo
     9 {
    10 public:
    11     Foo(int x):_x(x) {}
    12     ~Foo() {}
    13     void print() { cout << _x; }
    14     int getX() { return _x; }
    15 private:
    16     int _x;
    17 };
    18 
    19 bool badValue(Foo& f)
    20 {
    21     if (f.getX() == 2) {
    22         return true;
    23     }
    24     return false;
    25 }
    26 
    27 int main()
    28 {
    29         list<Foo> c;
    30 
    31     for (int i = 0; i < 10; ++i) {
    32         Foo f(i);
    33         c.push_back(f);
    34     }
    35 
    36     for (int i = 0; i < 10; ++i) {
    37         Foo f(i);
    38         c.push_back(f);
    39     }
    40 
    41     for (auto i : c) {
    42         i.print();
    43         cout << " ";
    44     }
    45     cout << endl;
    46 
    47     c.remove_if(badValue);
    48 
    49     for (auto i : c) {
    50         i.print();
    51         cout << " ";
    52     }
    53     cout << endl;
    54 }


    对于关联容器,则没有这么简单了。由于关联容器没有提供类似 remove_if 的成员函数。必须写一个循环来遍历 c 中的元素,并在遍历过程中删除元素。

    但是其中有一个陷阱:当容器中的一个元素被删除时,指向该元素的所有迭代器都将变得无效,一旦 c.erase(i) 返回,i 将成为无效值。

    为了避免这个问题,我们要确保在调用 erase 之前,有一个迭代器指向 c 中的下一个元素。最简单的办法是,对 i 使用后缀递增

     1 #include <iostream>
     2 #include <memory>
     3 #include <set>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 bool badValue(int x)
     9 {
    10     if (x == 2) {
    11         return true;
    12     }
    13     return false;
    14 }
    15 
    16 int main()
    17 {
    18         set<int> c;
    19 
    20     for (int i = 0; i < 10; ++i) {
    21         c.insert(i);
    22     }
    23 
    24     for (auto i : c) {
    25         cout << i << " ";
    26     }
    27     cout << endl;
    28 
    29     for (auto i = c.begin(); i != c.end(); /* 什么也不做 */) {
    30         if (badValue(*i)) {
    31             c.erase(i++); // 把当前的 i 传给 erase,并且在 erase 开始前递增了 i,使 i 能够正确的指向下一个元素 ( 为什么 ? )
    32         } else {
    33             ++i;
    34         }
    35     }
    36 
    37     for (auto i : c) {
    38         cout << i << " ";
    39     }
    40     cout << endl;
    41 }

    (答案:因为关联容器不是连续内存存储的,所以 i++ 后指向下个元素的迭代器还是有效的。如果是连续内存的容器(vector、string、deque),这样是错误的,因为调用 erase 不仅会使指向被删除元素的迭代器失效,也会使被删除元素之后的所有迭代器都无效(因为元素移动)。所以应该使用: i = c.erase(i); 因为 erase 会返回被删除元素)。

    结论:

    • 要删除容器中有特定值的所有对象

        如果容器是 vector、string 或 deque,则使用 erase-remove 习惯用法

        如果容器是 list,则使用 list::remove 成员函数

        如果容器是一个标准关联容器,则使用它的 erase 成员函数

    • 要删除容器中满足特定判别式(条件)的所有对象

        如果容器是 vector、string 或 deque,则使用 erase-remove_if 习惯用法

        如果容器是 list,则使用 list::remove_if 成员函数

        如果容器是一个标准关联容器,则使用 remove_copy_if 和 swap,或者写一个循环来遍历容器中的所有元素,记住当把迭代器传给 erase 时,要对它进行后缀递增

    • 要在循环内做某些(除了删除对象之外的)操作(比如记录日志)

        如果容器是一个标准序列容器,则写一个循环来遍历容器中的元素,记住每次调用 erase 时,要用它的返回值更新迭代器。

        如果容器是一个标准关联容器,则写一个循环来遍历容器中的元素,记住当把迭代器传给 erase 时,要对迭代器做后缀递增。

        

  • 相关阅读:
    八数码难题 (codevs 1225)题解
    小木棍 (codevs 3498)题解
    sliding windows (poj 2823) 题解
    集合删数 (vijos 1545) 题解
    合并果子 (codevs 1063) 题解
    等价表达式 (codevs 1107)题解
    生理周期 (poj 1006) 题解
    区间 (vijos 1439) 题解
    区间覆盖问题 题解
    种树 (codevs 1653) 题解
  • 原文地址:https://www.cnblogs.com/jingyg/p/5610426.html
Copyright © 2011-2022 走看看