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 时,要对迭代器做后缀递增。

        

  • 相关阅读:
    JavaScript学习(一)
    CSS学习(1)(网页编程)
    学习MVC3(二)——创建自己的第一个网页:实现用户登陆(1)
    C#多态小结——面向对象编程的三大机制之二
    学习网页编程(一)
    开始的2012
    基于jquery的上传插件Uploadify 3.1.1在MVC3中的使用:上传大文件的IO Error问题
    网页编程注意
    基于jquery的上传插件Uploadify 3.1.1在MVC3中的使用
    BackgroundSize
  • 原文地址:https://www.cnblogs.com/jingyg/p/5610426.html
Copyright © 2011-2022 走看看