《Effective STL 读书笔记》 第七章 在程序中使用STL
2011-09-12 01:57 咆哮的马甲 阅读(471) 评论(0) 编辑 收藏 举报作者:咆哮的马甲
出处:http://www.cnblogs.com/arthurliu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
转载请保持文档的完整性,严禁用于任何商业用途,否则保留追究法律责任的权利。
第四十三条:算法调用优先于手写的循环
算法往往作用于一对迭代器所指定的区间中的每一个元素上,所以算法的内部实现是基于循环的。虽然说类似于find和find_if的算法可能不会遍历所有的元素就返回了结果,但是在极端情况下,还是需要遍历全部的元素。
从以下几点分析,算法调用是优于手写的循环的
- 效率
- 正确性
- 可维护性
第四十四条:容器的成员函数优于同名的算法
- 成员函数速度优于同名算法
- 成员函数与容器的联系更加紧密
对于关联容器请看下面的例子:
1 set<int> s;
2
3 set<int>::iterator i1 = s.find(727);
4
5 set<int>::iterator i2 = find(s.begin(), s.end(), 727);
对于set而言,它的find成员函数的时间复杂度是log(n),而算法find的时间复杂度是线性的n。明显,成员函数的效率要远高于算法。
另外,算法是基于相等性而关联容器基于等价性,在这种情况下,调用成员函数和调用算法可能会得到不同的结果。(参见第19条)
对于map以及multimap,成员函数之针基于key进行操作,而算法基于key-value pair进行操作。
对于list而言,成员函数相对于算法的优势更加明显。算法是基于元素的拷贝的,而list成员函数可能只需要修改指针的指向。
还有之前所提到的list的remove成员函数,同时起到了remove和erase的作用。
有些算法,例如sort并不能应用在list上,因为sort是基于随机访问迭代器的。还有merge算法,它要求不能修改源区间,而merge成员函数总是在修改源链表的元素的指针指向。
第四十五条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range
- count: 区间内是否存在某个特定的值,如果存在的话,这个值有多少个拷贝。
- find: 区间内时候存在某个特定的值,如果存在的话,第一个符合条件的值在哪里。
- binary_search:一个排序的区间内是否存在一个特定的值。
- lower_bound:返回一个迭代器,或者指向第一个满足条件的元素,或者指向适合于该值插入的位置。切记lower_bound是基于等价性的,用相等性来比较lower_bound的返回值和目标元素是存在潜在风险的。
- upper_bound:返回一个迭代器,指向最后一个满足条件元素的后面一个元素。
- equal_range:返回一对迭代器,第一个指向lower_bound的返回值,第二个指向upper_bound的返回值。如果两个返回值指向同一位置,则说明没有符合条件的元素。Lower_bound与upper_bound的distance可以求得符合条件的元素的个数。
下表总结了在什么情况下使用什么样的算法或成员函数
对于multi容器来说,find并不能保证找出的元素是第一个具有此值的元素。如果希望找到第一个元素,必须通过lower_bound,然后在通过等价性的验证。Equal_range是另外一种方式,而且可以避免等价性测试,只是equal_range的开销要大于lower_bound。
第四十六条:考虑使用函数对象而不是函数作为STL算法的参数
函数对象优于函数的第一个原因在于函数对象的operator方法可以被优化为内联函数,从而使的函数调用的开销在编译器被消化。而编译器并没有将函数指针的间接调用在编译器进行优化,也就是说,函数作为STL算法的参数相对于函数对象而言,具有函数调用的开销。
第二个理由是某些编译器对于函数作为STL的参数支持的并不好。
第三个理由是有助于避免一些微妙的、语言本身的缺陷。比如说实例化一个函数模板,可能会与其他已经预定义的函数产生冲突。
第四十七条:避免产生“直写型”(write-only)的代码
根据以往的经验,代码被阅读的次数要远远多于被编写的次数,所以要有意识的写出具备可读性的代码。对于STL而言,则是尽量避免“直写型”的代码。
直写型的代码是这样的,对于程序的编写者而言,它显得非常的直接,并且每一步都符合当初设计的逻辑。但是对于程序的阅读者来说,在没有全面了解程序编写者动机的前提下,这样的代码往往让人一头雾水。
1 v.erase(remove_if(find_if(v.rbegin(),v.rend(),bind2nd(greater_equaql<int>(),y)).base()),v.end(),bind2nd(less<int>(),x));
比较易读的写法最好是这样的
// 初始化range_begin,使它指向v中大于等于y的最后一个元素之后的那个元素
// 如果不存在这样的元素,则rangeBegin被初始化为v.begin()
// 如果这个元素恰好是v的最后一个元素,则range_begin将被初始化为v.end()
VecIt rangeBegin = find_if(v.rbegin(),v.rend(),bind2nd(greater_equal<int>(),y)).base();
// 从rangeBegin到v.end()的区间中,删除所有小于x的值
v.erase(remove_if(rangeBegin,v.end(),bind2nd(less<int>(),x)),v.end());
第四十八条 总是include正确的头文件
与STL头文件相关的一些总结
- 几乎所有的STL容器都被声明在与之同名的头文件之中
- 除了accumulate、inner_product、adjacent_difference和partial_sum被声明在<numeric>中之外,其他都所有算法都声明在<algorithm>中
- 特殊类型的迭代器,例如isteam_iterator和istreambuf_iterator,都被声明在<iterator>中
- 标准的函数子,比如less<T>,和函数子配接器,比如not1、bind2nd都被声明在<functional>中。
第四十九条 学会分析与STL相关的编译器诊断信息
STL的编译错误信息往往冗长而且难以阅读,通过文本替换将复杂的容器名称替换为简单的代号,可以使得错误信息得到简化。
例如,将std::basic_string<char, std::char_traits<char>, std::allocator<char>>替换为可读性更强的string。
下面列举一些常见的STL错误,以及可能的出错原因
- Vector和string的迭代器通常就是指针,当错误的使用iterator的时候,编译器的错误信息中可能会包含指针类型的错误。
- 如果诊断信息提到了back_insert_iterator, front_insert_iterator和insert_iterator,则几乎意味着程序中直接或间接地调用了back_inserter, front_inserter或者是inserter。
- 输出迭代器以及inserter函数返回的迭代器在赋值操作符内部完成输入或者插入操作,如果有赋值操作符有关的错误信息,可以关注这些迭代器。
- 如果错误信息来自于算法的内部实现,往往意味着传递给算法的对象使用了错误的类型。
- 如果在使用一个常见的STL组件,但编译器却不认知,可能是没有包含合适的头文件。
第五十条 : 熟悉与STL相关的Web站点
SGI STL http://www.sgi.com/tech/stl
STLport http://www.stlport.org
Boost http://www.boost.org
另外个人推荐一个中文站点http://stlchina.huhoo.net/
作者:咆哮的马甲
出处:http://www.cnblogs.com/arthurliu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
转载请保持文档的完整性,严禁用于任何商业用途,否则保留追究法律责任的权利。