迅速读懂:Effective STL (五)

  这是《Effective STL》笔记最后一期,不能涵盖全部内容,书后仍然有些附加内容,不在附加,有兴趣可以找原书来读读,一则是区域设置后的忽略大小写比较,另一则是MSVC4-5编译器下STL注意事项

条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因

  函数和函数对象总使用用于非成员函数的语法形式调用。mem_fun带有一个到成员函数的指针,pmf,并返回一个mem_fun_t类型的对象。这是一个仿函数类,容纳成员函数指针并提供一个operator(),它调用指向在传给operator()的对象上的成员函数。mem_fun_t这样的类被称为函数对象适配器。类似的mem_fun_ref可以完成成员函数的调用。无论何时都可以使用ptr_fun完成,这两者的调用,但是会使得代码语义不明。

条款42:确定less<T>表示operator<

  试图修改std里的组件确实是禁止的(而且这么做通常被认为是行为未定义的范畴),但是在一些情况下,修补是允许的。具体来说,程序员被允许用自定义类型特化std内的模板。特化std模板的选择几乎总是更为优先,但很少发生,这确实合理的。例如,智能指针类的作者经常想让他们的类在排序的时候行为表现得像内建指针,因此用于智能指针类型的std::less特化并不罕见。当然这是允许的,因为这个less的特化仅仅在排序上保证智能指针的行为与它们的内建兄弟相同。

  operator<不仅是实现less的默认方式,它还是程序员希望less做的。让less做除operator<以外的事情是对程序员预期的无故破坏。如果你使用less(明确或者隐含),保证它表示operator<。如果你想要使用一些其他标准排序对象,建立一个特殊的不叫做less的仿函数类。

条款43:尽量用算法调用代替手写循环

  每个算法接受至少一对用来指示将被操作的对象区间的迭代器。当算法被执行时,它们必须检查指示给它的区间中的每个元素,并且是按你所期望的方式进行的:从区间的起始点循还到结束点。所以,算法内部是一个循环。此外,STL算法的广泛涉及面意味着很多你本来要用循环来实现的任务,现在可以改用算法来实现了。

  • 效率:算法通常比程序员产生的循环更高效。每次循环都要和出循环条件作比较,而算法for_each只需要比较一次;库的实现者可以利用他们知道容器的具体实现的优势,用库的使用者无法采用的方式来优化遍历;除了最微不足道的STL算法,所有的STL算法使用的计算机科学都比一般的C++程序员能拿得出来的算法复杂且高效。
  • 正确性:写循环时比调用算法更容易产生错误。难以正确实现循环的情况太多了,因为在使用迭代器前,必须时刻关注它们是否被不正确地操纵或变得无效。
  • 可维护性:算法通常使代码比相应的显式循环更干净、更直观。当你看见for、while或do的时候,你能知道的只是这是一种循环。如果要获得哪怕一点关于这个循环作了什么的信息,你就得审视它。算法则不用。一旦你看见调用一个算法,独一无二的名字勾勒出了它所作所为的轮廓。当然要了解它真正做了什么,你必须检查传给算法的实参,但这一般比去研究一个普通的循环结构要轻松得多。

  在算法调用与手写循环正在进行的较量中,关于代码清晰度的底线是:这完全取决于你想在循环里做的是什么。如果你要做的是算法已经提供了的,或者非常接近于它提供的,调用泛型算法更清晰。如果循环里要做的事非常简单,但调用算法时却需要使用绑定和适配器或者需要独立的仿函数类,你恐怕还是写循环比较好。最后,如果你在循环里做的事相当长或相当复杂,天平再次倾向于算法。因为长的、复杂的通常总应该封装入独立的函数。只要将循环体一封装入独立函数,你几乎总能找到方法将这个函数传给一个算法(通常是for_each),以使得最终代码直截了当。

条款44:尽量用成员函数代替同名的算法

  有些容器拥有和STL算法同名的成员函数。大多数情况下,你应该用成员函数代替算法。这样做有两个理由。首先,成员函数更快。其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。那是因为同名的算法和成员函数通常并不是是一样的。

  对于标准的关联容器,选择成员函数而不是同名的算法有几个好处。首先,你得到的是对数时间而不是线性时间的性能。其次,你判断两个元素“相同”使用的是等价,这是关联容器的默认定义。第三,当操纵map和multimap时,你可以自动地只处理key值而不是(key, value)对。

  list成员函数的行为和它们的算法兄弟的行为经常不相同。如果你真的想从容器中清除对象的话,调用remove、remove_if和unique算法后,必须紧接着调用erase函数;但list的remove、remove_if和unique成员函数真的去掉了元素,后面不需要接着调用erase。在sort算法和list的sort成员函数间的一个重要区别是前者不能用于list。作为单纯的双向迭代器,list的迭代器不能传给sort算法。merge算法和list的merge成员函数之间也同样存在巨大差异。这个算法被限制为不能修改源范围,但list::merge总是修改它的宿主list。

条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别

你想知道的 使用的算法 使用的成员函数
无序区间 有序区间 set/map multiset/map
期望值是否存在 find binary_search count find

期望值是否存在?
如果有,第一个等
于这个值的对象在
哪里?

find equal_range find
lower_bound

find


第一个不在期望值
之前的对象在哪
里?

find_if lower_bound lower_bound lower_bound

第一个在期望值之
后的对象在哪里?

find_if upper_bound upper_bound upper_bound

有多少对象等于期
望值?

 count
equal_range

然后distance

count  count 

等于期望值的所有
对象在哪里?

 find(迭代)  equal_range equal_range  equal_range 

条款46:考虑使用函数对象代替函数作算法的参数

  一个关于用高级语言编程的抱怨是抽象层次越高,产生的代码效率就越低。操作包含一个double的类产生的代码效率比对应的直接操作一个double的代码低。因此你可能会奇怪地发现把STL函数对象——化装成函数的对象——传递给算法所产生的代码一般比传递真的函数高效。这个行为的解释很简单:内联。如果一个函数对象的operator()函数被声明为内联(不管显式地通过inline或者隐式地通过定义在它的类定义中),编译器就可以获得那个函数的函数体,而且大部分编译器喜欢在调用算法的模板实例化时内联那个函数。在上面的例子中,greater<double>::operator()是一个内联函数,所以编译器在实例化sort时内联展开它。结果,sort没有包含一次函数调用,而且编译器可以对这个没有调用操作的代码进行其他情况下不经常进行的优化。

  还有另一个使用函数对象代替函数作为算法参数的理由,STL平台经常完全拒绝有效代码,即使编译器或库或两者都没问题。解决方式就是创建仿函数类,仿函数类的创造不仅避开了编译器一致性问题,而且可能会带来性能提升。

  另一个用函数对象代替函数的原因是它们可以帮助你避免细微的语言陷阱。有时候,看起来合理代码被编译器由于合法性的原因——但很模糊——而拒绝。

条款47:避免产生只写代码

  当你写代码时,它似乎很直截了当,因为它是一些基本想法(也就是,erase-remove惯用法加上使用逆向迭代器调用find的想法)的自然产物。但是,读者们很难把最后的产物分解回它基于的想法。这就被称为只写代码:很容易写,但很难读和理解。代码的读比写更经常,这是软件工程的真理。也就是说软件的维护比开发花费多得多的时间。不能读和理解的软件不能被维护,不能维护的软件几乎没有不值得拥有。你用STL越多,你会感到它越来越舒适,而且你会越来越多的使用嵌套函数调用和即时(on the fly)建立函数对象。

条款48:总是#include适当的头文件

  • 几乎所有的容器都在同名的头文件里,比如,vector在<vector>中声明,list在<list>中声明等。例外的是<set>和<map>。<set>声明了set和multiset,<map>声明了map和multimap。
  • 除了四个算法外,所有的算法都在<algorithm>中声明。例外的是accumulate、inner_product、adjacent_difference和partial_sum。这些算法在<numeric>中声明。
  • 特殊的迭代器,包括istream_iterators和istreambuf_iterators,在<iterator>中声明。
  • 标准仿函数(比如less<T>)和仿函数适配器(比如not1、bind2nd)在<functional>中声明。

条款49:学习破解有关STL的编译器诊断信息

  举个例子:

1     string s(10); // 常识建立一个大小为10的string

  这段代码是不能通过编译的,提示信息如下:

error C2664:

  ‘__thiscall std::basic_string<char, structstd::char_traits<char>,class std::allocator<char> >::std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >  (const class std::allocator<char> &)‘: cannot convert parameter 1 from ‘const int‘ to ‘constclass std::allocator<char> &‘

  Reason: cannot convert from ‘const int‘ to ‘constclass std::allocator<char>

  No constructor could take the source type, or constructor overload resolution was ambiguous

  string不是一个类,它是typedef。实际上,它是这个的typedef:basic_string<char, char_traits<char>, allocator<char> >。用文字“string”全局替换冗繁难解的basic_string:

error C2664:

  ‘__thiscall string::string(const classstd::allocator<char> &)‘: cannot convert parameter 1 from ‘const int‘ to const class std::allocator<char> &‘

  就可以比较容易的看出是没有相应的参数转换,而实际上string没有传int参数的构造函数。

  • 对于vector和string,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。
  • 提到back_insert_iterator、front_insert_iterator或insert_iterator的消息经常意味着你错误调用了back_inserter、front_inserter或inserter,一一对应。
  • 类似地,如果你得到的一条消息提及binder1st或binder2nd,你或许错误地使用了bind1st或bind2nd。(c++11很少使用这一条,多使用bind)
  • 输出迭代器(例如ostream_iterator、ostreambuf_iterators,和从back_inserter、front_inserter和inserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。
  • 你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在<algorithm>中),也许是你试图给那算法用的类型出错了。
  • 你使用常见的STL组件比如vector、string或for_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件

条款50:让你自己熟悉有关STL的网站

  作者还推了一堆书,我觉得要看完估计很久,如果真的需要的话,建议去原著上面找找,这里就不发了。

《Effective STL》到此结束。

时间: 2024-10-06 16:32:34

迅速读懂:Effective STL (五)的相关文章

Effective STL读书摘要(一)

一直在用STL,认为对STL也有一些理解,比如比较函数怎么写,什么情况下用什么容器效率高,但是当你读过Effective STL之后才知道这远远不够,之前的代码还有很多可以优化的空间,下面我会罗列一些映像比较深的点,比较偏向代码因为这样可以方便以后的调用.这里是到Item29,余下的留下次看. 1) 检查容器是否为空 if(c.empty()){}   better than if(c.size()==0){} 2)如果能用批量操作函数就不要用循环来做 批量操作可以提高效率,要有能用批处理尽量批

五分钟读懂UML类图

平时阅读一些远吗分析类文章或是设计应用架构时没少与UML类图打交道.实际上,UML类图中最常用到的元素五分钟就能掌握,下面赶紧来一起认识一下它吧: 一.类的属性的表示方式 在UML类图中,类使用包含类名.属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和email这3个属性,以及modifyInfo()方法. 那么属性/方法名称前加的加号和减号是什么意思呢?它们表示了这个属性或方法的可见性,UML类图中表示可见性的符

五分钟读懂UML类图(转)

平时阅读一些远吗分析类文章或是设计应用架构时没少与UML类图打交道.实际上,UML类图中最常用到的元素五分钟就能掌握,下面赶紧来一起认识一下它吧: 一.类的属性的表示方式 在UML类图中,类使用包含类名.属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和email这3个属性,以及modifyInfo()方法. 那么属性/方法名称前加的加号和减号是什么意思呢?它们表示了这个属性或方法的可见性,UML类图中表示可见性的符

读懂diff【转】

本文转载自:http://www.ruanyifeng.com/blog/2012/08/how_to_read_diff.html 读懂diff 作者: 阮一峰 日期: 2012年8月29日 diff是Unix系统的一个很重要的工具程序. 它用来比较两个文本文件的差异,是代码版本管理的基石之一.你在命令行下,输入: $ diff <变动前的文件> <变动后的文件> diff就会告诉你,这两个文件有何差异.它的显示结果不太好懂,下面我就来说明,如何读懂diff. 一.diff的三种

区块链产业生态、存在问题及政策建议|一文读懂新趋势

区块链产业生态.存在问题及政策建议|一文读懂新趋势 2017-03-03 09:47:50  来源: 腾讯研究院抢沙发 摘要:从技术上来讲,区块链是一种分布式的记账方法.说到记账,我们经历了从实物记账向电子记账的演变关键词: 区块链 中国信息通信研究院与腾讯研究院区块链联合课题组 卿苏德,中国信息通信研究院区块链研究团队研究员,主要研究方向为区块链和人工智能等. 一.区块链技术原理和发展趋势 01| 区块链--一种分布式记账方法 从技术上来讲,区块链是一种分布式的记账方法.说到记账,我们经历了从

读懂diff

转自:http://www.ruanyifeng.com/blog/2012/08/how_to_read_diff.html 作者: 阮一峰 日期: 2012年8月29日 diff是Unix系统的一个很重要的工具程序. 它用来比较两个文本文件的差异,是代码版本管理的基石之一.你在命令行下,输入: $ diff <变动前的文件> <变动后的文件> diff就会告诉你,这两个文件有何差异.它的显示结果不太好懂,下面我就来说明,如何读懂diff. 一.diff的三种格式 由于历史原因,

读懂Redis并配置主从集群及高可用部署

一.背景 运维工作尤其是linux运维,其实最考验你的能力,因为需要学习的东西实在太多, 你既要懂网络:思科华为设备的配置: 要懂性能调优:包括lamp或者lnmp的性能调优,也包括linux操作系统调优: 要懂数据库mysql或者nosql(例如mongodb): 要懂编程语言:Shell是最基本的,还要学习perl,python,甚至ruby和C++等(因为一些软件是这些语言编写的),还得熟练掌握awk,sed,grep以及正则表达式: 要懂一些调试排错的命令工具的使用,比如htop,dst

读懂IL代码就这么简单(一)

一前言 感谢 @冰麟轻武 指出文章的错误之处,现已更正 对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道你的代码是如何运行相互调用的,此言一出不明觉厉. 然后开始接触IL,了解了一段时后才发现原来读懂IL代码并不难.进入正题   1.1  什么是IL IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直

写让别人能读懂的代码

随着软件行业的不断发展,历史遗留的程序越来越多,代码的维护成本越来越大,甚至大于开发成本.而新功能的开发又常常依赖于旧代码,阅读旧代码所花费的时间几乎要大于写新功能的代码. 我前几天看了一本书,书中有这么一句话: “复杂的代码往往都是新手所写,只有经验老道的高手才能写出简单,富有表现力的代码” 此话虽然说的有点夸张,可是也说明了经验的重要性. 我们所写的代码除了让机器执行外,还需要别人来阅读.所以我们要: 写让别人能读懂的代码 写可扩展的代码 写可测试的代码(代码应该具备可测试性,对没有可测试性