条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator
有些函数只接受iterator类型的参数。
const_iterator不能隐式转换成iterator,即使用变通的办法,也不通用,且不能保证高效。
从reverse_iterator转换而来的iterator在转换之后可能需要相应的调整。
还有种情况,如果const_iterator将operator==实现为了成员函数的话,则在==的左边必须是const_iterator才能编译通过。
不过,const_iterator可以防止所指元素被改变,有时候也是有用的。
条款27:用distance和advance把const_iterator转化成iterator
//ci是const_iterator,Iter i是iterator
Iter i(const_cast<Iter>(ci)); // 错误!不能从const_iterator映射为iterator
只所以错误,是因为const_iterator与iterator是完全不同的类。(如果是vector和string容器的迭代器的话,有可能可以通过编译,因为这些容器通常用真实的指针作为它们的迭代器。)
用如下的方法来转换:
Iter i(d.begin()); // 初始化i为d.begin()
advance(i, distance<ConstIter>(i, ci)); // 把i移到指向ci位置
条款28:了解如何通过reverse_iterator的base得到iterator
vector<int>::reverse_iterator ri;
vector<int>::iterator i(ri.base());
实际上得到的i是指向ri的下一个位置的元素,而并不是ri所指的元素。
这样做的好处是在插入元素的时候,并不需要考虑它们的位置,不管用i还是ri来插入元素,都会插入到相同的位置。
但是,在擦除元素的时候就会出现问题。因为erase是插除迭代器本身所指的元素,所以i和ri会擦除不同的元素。
解决方法如下:
v.erase((++ri).base()); // 删除ri指向的元素;
条款29:需要一个一个字符输入时考虑使用istreambuf_iterator
istream_iterators所依靠的operator>>函数进行的是格式化输入,这意味着每次你调用的时候它们都必须做大量工作。如它们必须检查影响它们行为的流标志,读取错误检查等等。所以它的速度相对比较慢。
你可以像istream_iterator一样使用istreambuf_iterator,但istream_iterator<char>对象使用operator>>来从输入流中读取单个字符。,istreambuf_iterator不忽略任何字符。它们只简单地抓取流缓冲区的下一个字符。
条款30:确保目标区间足够大
如果目标区间并不足够大,用back_inserter或front_inserter或inserter来插入。
赋值只在两个对象之间操作时有意义,而不是在一个对象和一块原始的比特之间。
results.reserve(results.size() + values.size());
transform(values.begin(), values.end(),
results.end(), // 到未初始化的内存
transmogrify); // 行为未定义!
因为reserve出来的空间只是原始内存,里面并不包含对象。
条款31:了解你的排序选择
partial_sort只排序前N个元素。
nth_element本质上等价于partial_sort,但是它并不在这前N个元素内部排序。但前N个是所有元素里面N个最大的。 它也可以将指定位置的单一元素设成与相应位置相符的大小。
partition排序指定范围内的元素。
条款32:如果你真的想删除东西的话就在类似remove的算法后接上erase
唯一从容器中除去一个元素的方法是在那个容器上调用一个成员函数,而且因为remove无法知道它正在操作的容器,所以remove不可能从一个容器中除去元素。从一个容器中remove元素不会改变容器中元素的个数,它只是将相应位置后面的元素向前移,而且不删除最后面的元素。 用size()取得的大小也不会变化。
所以要想删除元素,调用完remove后一定要调用erase。
v.erase(remove(v.begin(), v.end(), 99), v.end()); // 真的删除所有等于99的元素。
(不过list的成员函数remove可以真正的删除元素。)
条款33:提防在指针的容器上使用类似remove的算法
容器存储的是指针的时候,调用remove的时候,会使一些内存空间不被指针所指向,从而无法再引用这段内存空间,从而造成内存泄漏。
如果你无法避免在那样的容器上使用remove,排除这个问题一种方法是在应用erase-remove惯用法之前先删除指针并设置它们为空,然后除去容器中的所有空指针。
条款34:注意哪个算法需要有序区间
binary_search
lower_bound
upper_bound
equal_range
set_union
set_intersection
set_difference
set_symmetric_difference
merge
inplace_merge
includes
要注意容器的排序方式与算法的排序方式是否一致,如果不一致,可能导致一些未定义的行为。
条款35:通过mismatch或lexicographical比较实现简单的忽略大小写字符串比较
mismatch,用来返回两个字符串第一个不匹配的位置。只要为它指定比较规则即可,第一个区间要小于第二个区间的长度。
lexicographical_compare用来进行字典排序。
条款36:了解copy_if的正确实现
条款37:用accumulate或for_each来统计区间
条款38:把仿函数类设计为用于值传递
c/c++准则:函数指针是值传递。
STL算法库一般要按值传递来接收函数对象。不过可以通过显示指定算法的模板参数类型,来传递引用。不过传递引用时,有时候会发生编译错误。
用值传递函数对象时,函数对象要尽可能的小,否则它们的拷贝会很昂贵。函数对象必须是单态的,不能用虚函数。因为派生类以值传递的时候可能造成切割问题。
如果要传递的函数对象很大,或者要使用到多态,用Bridge模式可以解决。 实现方式即:
带着你要放进你的仿函数类的数据和/或多态,把它们移到另一个类中(即实现类)。然后给你的仿函数一个指向这个新类的指针。建立一个包含一个指向实现类的指针的小而单态的类,然后把所有数据和虚函数放到实现类。
条款39:用纯函数做判断式
判断式是返回bool(或者其他可以隐式转化为bool的东西)。
纯函数是返回值只依赖于参数的函数。且它的内部不会改变参数的值,由纯函数引用的所有数据不是作为参数传进的就是在函数生存期内是常量。所以纯函数没有状态。
一个判断式类是一个仿函数类,它的operator()函数是一个判断式,也就是,它的operator()返回true或false(或其他可以隐式转换到true或false的东西)。
如果在判断式中改变了所引用的数据,即不是纯函数了,这时候,以值来传递判断式的时候,可能会出现问题,比如说判断式本应该只有一次返回true,但两次使用这个判断式时,两次被重新构造,有两次返回true了。结果就会有问题了。
最简单的使你自己不摔跟头而进入语言陷阱的方法是在判断式类中把你的operator()函数声明为const。这样,编译器不会让你在里面改变任何类数据成员。
不管怎么写判断式,他们都应该是纯函数。
条款40:使仿函数类可适配
提供必要的typedef的函数对象称为可适配的。(typedef是:argument_type、first_argument_type、second_argument_type和result_type)
ptr_fun做的唯一的事是使一些typedef有效。
只要使我们的类继承自std::unary_function或是std::binary_function模板类,就可以自动使这个函数对象变为可适配的。
在仿函数类中,只能提供一个operator()来适配一元或二元的仿函数,如果提供两个,它只能适配一个!
条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因
这些函数的主要任务之一是覆盖C++固有的语法矛盾之一。
它们被称为函数对象适配器。
在将成员函数传递给STL组件时,一定得使用mem_fun和mem_fun_ref,否则不能通过编译。
ptr_fun是在函数对象没有提供一些typedef时,要使用的。(参看条款40)
条款42:确定less<T>表示operator<
条款43:尽量用算法调用代替手写循环
调用算法通常比手写的循环更优越。原因:
● 效率:算法通常比程序员产生的循环更高效。
● 正确性:写循环时比调用算法更容易产生错误。
● 可维护性:算法通常使代码比相应的显式循环更干净、更直观。
条款44:尽量用成员函数代替同名的算法
首先,成员函数更快。其次,比起算法来,它们与容器结合得更好
因为成员函数就是针对某容器提供的特化版本算法。
list成员函数的行为和它们的算法兄弟的行为经常不相同。如调用通用的remove、remove_if和unique算法后,必须紧接着调用erase函数才能清除对象,但list的remove等成员函数后面并不需要erase函数,就能清除对象。
条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别
如果迭代器定义了一个有序区间,则可以通过binary_search、lower_bound、upper_bound和equal_range来加速。如果没有有序区间,则只能用count、find线性时间的算法。count经常用来检查元素是否存在。但count找到存在的元素后,仍会继续查找,直到将所有元素遍历。但find找到后就会结束,所以find的效率可能略优一点。
要测试在有序区间中是否存在一个值,使用binary_search,但这个算法只返回一个bool值,表明是否找到了,要想得到更多的信息,则这个算法无能为力了。如果想知道存在的元素在哪儿,可以用equal_range。
条款46:考虑使用函数对象代替函数作算法的参数
函数作为算法的参数的时候,会传递函数的指针作为参数,而且函数指针会抑制内联。函数对象的operator()被声明为内联的时候,在速度上,会高于以函数作为算法的参数进行传递。
有时候,用函数作为参数的时候,可能会编译不过,但用函数对象,一般都不会出问题。
条款47:避免产生只写代码
此处的“只写”代码是指,容易写,但是不容易读和理解的代码。
代码的读比写更经常,这是软件工程的真理。
条款48:总是#include适当的头文件
条款49:学习破解有关STL的编译器诊断信息
条款50:让你自己熟悉有关STL的网站