Effective STL - 06 - Programming by STL
Programming by STL
条款43:尽量用算法调用代替手写循环
- 如果你要做的是算法已经提供了的,或者非常接近于它提供的,调用泛型算法更清晰。
- 如果循环里要做的事非常简单,但调用算法时却需要使用绑定和适配器或者需要独立的仿函数类,你恐怕还是写循环比较好。
- 最后,如果你在循环里做的事相当长或相当复杂,天平再次倾向于算法。
条款44:尽量用成员函数代替同名的算法
关联容器提供了count、find、lower_bound、upper_bound和equal_range,而list提供了remove、remove_if、unique、sort、merge和reverse。大多数情况下,你应该用成员函数代替算法。这样做有两个理由。首先,成员函数更快。其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。那是因为同名的算法和成员函数通常并不是是一样的。
1 |
|
成员函数使用了与 关联容器 一致的等价算法,而 find 使用的是 相等 算法。因此,如果使用关联容器的话,你应该尽量使用成员函数形式的find、count、lower_bound等等,而不是同名的算法,因为这些成员函数版本提供了和其它成员函数一致的行为。
list成员函数的行为和它们的算法兄弟的行为经常不相同。正如条款32所解释的,如果你真的想从容器中清除对象的话,调用remove、remove_if和unique算法后,必须紧接着调用erase函数;但list的remove、remove_if和unique成员函数真的去掉了元素,后面不需要接着调用erase。
条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别
你想知道的 | 在无序区间 | 在有序区间 | 在set或map上(成员函数) | 在multiset或multimap上(成员函数) |
---|---|---|---|---|
期望值是否存在? | find | binary_search | count | find |
期望值是否存在?如果有,第一个等于这个值的对象在哪里? | find | equal_range | find | find或lower_bound(参见下面) |
第一个不在期望值之前的对象在哪里? | 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 |
- 对于无序区间,大部分都是使用 find 或者 find_if
- 对于有序区间,可以使用 binary_search、lower_bound、upper_bound、equal_range
- 对于关联式容器,也可以用本身的成员函数
条款46:考虑使用函数对象代替函数作算法的参数
如果一个函数对象的operator()函数被声明为内联(不管显式地通过inline或者隐式地通过定义在它的类定义中),编译器就可以获得那个函数的函数体,而且大部分编译器喜欢在调用算法的模板实例化时内联那个函数。在上面的例子中,greater
它传了一个doubleGreater的指针,而指针的调用存在开销。
不过我实际测试来看,没有什么区别:
1 |
|
把函数对象作为算法的参数所带来的不仅是巨大的效率提升。在让你的代码可以编译方面,它们也更稳健。当然,真函数很有用,但是当涉及有效的STL编程时,函数对象经常更有用。
条款47:避免产生只写代码
代码的读比写更经常,这是软件工程的真理。也就是说软件的维护比开发花费多得多的时间。不能读和理解的软件不能被维护,不能维护的软件几乎没有不值得拥有。你用STL越多,你会感到它越来越舒适,而且你会越来越多的使用嵌套函数调用和即时(on the fly)建立函数对象。这没有什么错的,但永远记住你今天写的代码会被某个人——也可能是你——在未来的某一天读到。为那天做准备吧
条款48:总是#include适当的头文件
- 几乎所有的容器都在同名的头文件里,比如,vector在
中声明,list在 - 中声明等。例外的是
和 - 除了四个算法外,所有的算法都在
中声明。例外的是accumulate(参见条款37)、inner_product、adjacent_difference和partial_sum。这些算法在 中声明。 - 特殊的迭代器,包括istream_iterators和istreambuf_iterators(参见条款29),在
中声明。 - 标准仿函数(比如less
)和仿函数适配器(比如not1、bind2nd)在 中声明。
条款49:学习破解有关STL的编译器诊断信息
这里有一些应该能帮助你理解有关STL的编译器消息的其它提示:
对于vector和string,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。例如,如果你的源代码涉及vector
::iterator,编译器消息有时会提及double指针。(一个值得注意的例外是当你使用来自STLport的STL实现,而且你运行在调试模式。那样的话,vector和string的迭代器干脆不是指针。对STLport和它调试模式的更多信息,转向条款50。) 提到back_insert_iterator、front_insert_iterator或insert_iterator的消息经常意味着你错误调用了back_inserter、front_inserter或inserter,一一对应,(back_inserter返回back_insert_iterator类型的对象,front_inserter返回front_insert_iterator类型的对象,而inserter返回insert_iterator类型的对象。关于使用这些inserter的信息,参考条款30。)如果你没有调用这些函数,你(直接或间接)调用的一些函数做了。
类似地,如果你得到的一条消息提及binder1st或binder2nd,你或许错误地使用了bind1st或bind2nd。(bind1st返回binder1st类型的对象,而bind2nd返回binder2nd类型的对象。)
输出迭代器(例如ostream_iterator、ostreambuf_iterators(参见条款29),和从back_inserter、front_inserter和inserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。为了明白我的意思,试着编译这段代码:
1
2
3vector<string> v; // 试图打印一个
copy(v.begin(), v.end(), // string*指针的容器,
ostream_iterator<string>(cout, "\n")); // 被当作string对象你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在
中),也许是你试图给那算法用的类型出错了。例如,你可能传了错误种类的迭代器。要看看这样的用法错误是怎样报告的,通过把这段代码喂给你的编译器来启发(并愉快!)自己: 1
2list<int>::iterator i1, i2; // 把双向迭代器
sort(i1, i2); // 传给一个需要 // 随机访问迭代器的算法你使用常见的STL组件比如vector、string或for_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件。正如条款48的解释,这问题会降临在长期以来都可以顺利编译而刚移植到新平台的代码
有经验的STL程序员发展出一项类似的技能。他们可以不假思索地在内部把比如std::basic_string<char, struct std::char_traits
条款50:让你自己熟悉有关STL的网站
- SGI STL网站,http://www.sgi.com/tech/stl/。
- STLport网站,http://www.stlport.org/。
- Boost网站,http://www.boost.org/。如果无法访问,尝试:Boost C++ Libraries