今天看书刚刚看的,就记录下来吧。这可能是老生常谈了,权且作为一个警醒的例子吧。

大家都知道STL有两个非常重要的组成部分,容器和算法。

算法就是一个个的函数,通过迭代器和容器关联在一起,完成一些工作。

算法和容器的分离为程序设计提供了很大的灵活性,但是也带来了一些负面效果,下面我讲的这个问题就是一个例子。

STL的算法里有一个remove函数,而list自身也有一个remove函数,功能都是一样的,移除某一个元素,那我们应该使用哪一个呢?

看一下下面这段程序

 1     list<int> numbers;
 2
 3     for ( int number = 0; number <= 6; number ++ ) {
 4         numbers.push_front(number);
 5         numbers.push_back(number);
 6     }
 7
 8     copy(numbers.begin(), numbers.end(),
 9             ostream_iterator<int>(cout, " "));
10     cout << endl;
11
12     // remove algorithm will remove element but not erase the element from container
13     // it will return the logical desination of container
14     list<int>::iterator endOfNumbers = remove(numbers.begin(), numbers.end(), 3);
15
16     copy(numbers.begin(), numbers.end(),
17             ostream_iterator<int>(cout, " "));
18     cout << endl;

输出是什么呢?

第一行肯定是6 5 4 3 2 1 0 0 1 2 3 4 5 6,那么第二行会输出什么?

如果是没有仔细看过STL的人肯定会认为remove(number.begin(), numbers.end(), 3)会移除所有值为3的元素。所以输出是:6 5 4 2 1 0 0 1 2 4 5 6。

但是,我们看一下它真正的输出:

6 5 4 2 1 0 0 1 2 4 5 6 5 6

你可能会非常惊讶,为什么最后会多出5和6两个数呢?

我们来讲一下remove算法的原理。

remove算法工作时并不是直接把元素删除,而是用后面的元素替代前面的元素,也即是说如果我对1234这个序列remove 2,返回的序列是 1344(3被复制到2的位置,4被复制到3的位置)。

这样上面的例子就好解释了,那两个3的元素并没有被移除,而是用后面的元素覆盖了前面的元素。多出的那两个数没有被移除掉而已。

那么我们应该如何真正完成移除呢?remove函数会返回一个迭代器,那个迭代器是这个序列的逻辑终点,也即是我代码里的endOfNumbers,它指向倒数第二个5上。

于是我们要利用list的erase函数完成元素移除

numbers.erase(endOfNumbers, numbers.end());

这样我们就完成了我们的工作,稍稍有点曲折……

其实我们可以把这两步放在一起,比如如果我想接着移除所有值为2的元素

numbers.erase(remove(numbers.begin(), numbers.end(), 2), numbers.end());

这样我们就可以一步到位了。

但是这样好么?

不好。

大家会发现,remove函数的原理是复制而不是指针的移动(因为函数操纵的是迭代器,而C++的迭代器没有定义删除操作),这样会带来一个问题:我们使用list是因为它的修改的效率非常高,改变一下指针就可以了。而这里我们复制了元素,如果在vector中,可能还是高效的,因为vector无论如何都要复制,而对于list就不是如此了,会极度降低我们的效率。

那我们怎么办呢?

答案是使用list自己的remove函数

numbers.remove(1);

我们可以这样删除所有值为1的元素。

也即是说,如果要删除list中的元素,我们应该使用list的remove成员函数,而不是remove算法

小结

我们都知道,STL是一个效率、复用性、灵活性折衷的产物,其中效率至关重要,所以STL已经禁止了一些效率低的操作(比如list的随机访问),而鼓励你去使用其它的容器。

但是,在算法中,为了灵活性,STL还是会牺牲一些东西,比如我们这个例子。

个人觉得,STL作为C++标准库的一个组成部分,特点和C++本身一模一样,强大而复杂,有些地方难以理解,很多细节需要学习注意,我们要学会避免陷入某些陷阱之中,比如这个例子就是一个效率陷阱。

其它更多的陷阱是错误处理方面的,STL本身并没有规定过多的错误处理,大部分的错误处理都交给了我们,理由很简单:性能至上,如果一个东西自身没有错误检查,我们可以包装一个带错误检查的类;但是如果这个东西自身就带了错误检查,那么我们就没有任何方法提升它的效率了。这也是很多C和C++库的设计原则。

所以,很多时候,需要我们深入细节,然后再决定到底怎么做。因为C++就是如此:有很多路可以走,需要我们自己选择最好的一条路。

STL的remove函数和list的remove成员函数相关推荐

  1. C++类的成员函数(在类外定义成员函数、inline成员函数)

    类的成员函数(简称类函数)是函数的一种,它的用法和作用和前面介绍过的函数基本上是一样的,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中.它可以被指定为private ...

  2. C++类的成员函数(在类外定义成员函数)

    类的成员函数(简称类函数)是函数的一种,它的用法和作用和前面介绍过的函数基本上是一样的,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中.它可以被指定为private ...

  3. JAVA面向对象中继承的子父类成员函数的内存图解,以及成员函数中的覆盖的应用.

    JAVA中继承子父类成员函数的使用 我把成员函数理解为就是类里面的功能,或者说是方法. 子父类的成员函数调用可以是这样的 例如: class Fu{void show1(){System.out.pr ...

  4. C++中 线程函数为静态函数 及 类成员函数作为回调函数(转载)

    C++中 线程函数为静态函数 及 类成员函数作为回调函数 线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运 ...

  5. C++《STL和泛型编程》容器不带/带有成员函数总结

    容器不带成员函数count(): array  vector  list  forward_list  deque 容器带有成员函数count(): set/multiset  map/multima ...

  6. c++中delete对象后 调用成员函数_C++类的特殊成员函数及default/delete特性

    本文包含以下内容 1. C++的四类特殊成员函数介绍,重点介绍拷贝构造函数和拷贝复制运算符 2. C++11中的default/delete特性 本文内容侧重个人理解,深入理解其原理推荐https:/ ...

  7. 抽象数据类型(顺序栈)、断言、包含头文件、内联函数、非内联成员函数[C++ In Action][4]...

    1. C++中的接口与实现思想, 即类的定义.数据成员的定义.函数原型在接口文件中进行, 实现代码放在实现文件中 2. 函数调用开销:调用前要先保存寄存器,并在返回点恢复:复制实参:程序必须转入一个新 ...

  8. 一般函数指针和类的成员函数指针

    转载请注明原文网址: http://www.cnblogs.com/xianyunhe/archive/2011/11/26/2264709.html 函数指针是通过指向函数的指针间接调用函数.函数指 ...

  9. 函数指针以及在类成员函数中应用函数指针

    什么是函数指针 如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址.而且函数名表示的就是这个地址.既然是地址我们就可以定义一个指针变 ...

  10. 函数指针调用类的成员函数

    1 在每个被调用函数之前加上static, 可以使成员函数脱离对象信息单独存在,虽然它属于这个类,但是没有附带上对象信息,但是前提是,static成员函数不能使用对象的信息(成员和函数). 2 使用一 ...

最新文章

  1. delphi中的函数传参如何传枚举参数_shell脚本的函数介绍使用和工作常用案例。建议收藏...
  2. [使用心得]maven2之m2eclipse使用手册之六使用Maven2插件创建一个简单的SSH2项目之jetty篇(一)...
  3. 如何在柱状图中点连线_练瑜伽,如何放松僵硬紧张的髂腰肌?
  4. 一些Xcode快捷键,给新手
  5. nssl1477-赛【对顶堆,贪心】
  6. centos6.5-64安装zabbix2.4
  7. POJ-英语数字转化器
  8. libpcap中主要函数使用介绍
  9. ActiveMQ的下载安装与操作示例
  10. 加入域的计算机如何本地用户登录,关于本地缓存登陆和域用户将计算机加入域的问题(转)...
  11. 良心分享!最全面cmd快捷指令及使用方法,万字总结
  12. python教你画一棵树
  13. VRRP:虚拟网关冗余技术
  14. Jay的小迷弟-字符串溢出处理取模例题
  15. windows版微信多开
  16. Android 无标题 全屏设置
  17. Dev C++下载及使用
  18. 离谱的布斯法(补码一位乘)
  19. SpringBoot集成elasticsearch使用
  20. 《Android Studio开发实战》学习(一)- Hello World

热门文章

  1. windows redis安装与配置
  2. 九眼智能:信息安全是网络发展的关键
  3. visual studio 的git插件推荐
  4. 使用cqengine进行集合检索
  5. JavaScript History对象
  6. 如何在友好的情景下向用户索取手机权限?
  7. Linux 运维自动化之Cobbler实战案例
  8. Sharepoint学习笔记 –架构系列—Sharepoint的客户端对象模型(Client Object Model)
  9. heartbeat+drbd+mysql构建mysql高可用群集
  10. Arrays.asList详解