Effective STL
第9条:慎重选择删除元素的方法
删除特定值元素,vector、string、deque用erase-remove:c.erase(remove(c.begin(),c.end(),1963),c.end());
list用c.remove();关联容器用c.erase().
vector、string、deque删除特定位置元素,用erase返回被删除元素下一位置的迭代器,因此用i=c.erase(i);
关联容器删除特定位置元素,则用c.erase(i++)。
第14条:使用reserve避免不必要的内存分配
size()容器多少元素;capacity()能容纳元素总数;resize()(Container::size_type n)强迫容器改变到包含n个元素的状态,n比当前size小,则尾部元素会被析构,大则通过默认构造函数创建新元素并加到末尾;reserve(Container::size_type n)把容器容量变为至少n。
第17条:使用swap技巧除去多余容量
vector<Contestant>(contestants).swap(contestants);创建一个临时矢量,它是contestants的副本,然后和contestants中数据做swap操作。
同样对string也适用:string(s).swap(s);
swap操作时,迭代器、指针、引用也被交换,因此原迭代器、指针、引用依然有效,只不过在另一容器中了。
第21条:总是让比较函数在等值情况下返回false
第22条:切勿直接修改set或multiset中的键
要修改set中的键值时,使用const_cast<E&>(*i).setTitle("New Title"),而不是static_cast<E>(*i).setTitle("New Title"),因为后者转换的结果是一个临时的匿名对象。
第24条:当效率至关重要时,请在map::operator[]与map::insert之间谨慎做出选择
添加元素时用insert,更新元素值时用operator[]。
第27条:使用distance和advance将容器的const_iterator转换为iterator
通过const_iterator得到iterator的一个方法:advance(i,distance<const_iterator>(i,ci));首先创建一个新的iterator i,指向容器起始位置,
然后计算两个迭代器(它们指向同一个容器)之间的距离,然后移动i使它与const_iterator指向相同元素。
第31条:了解各种与排序有关的选择
完全排序sort、stable_sort
只要求对前n个排序partial_sort
只找最前n个,但不对这前n个排序nth_element
标准序列容器中元素按照是否满足某条件区分开来,用partition和stable_partition
list有自己的sort算法,但由于没有随机访问迭代器,因此不能用partial_sort、nth_element等算法。
第32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase
remove不删除元素,只是把元素值用后面的元素覆盖,因此要真正删除需要使用容器的成员函数erase(remove接受迭代器作为参数,不接受容器作为参数,因此不知道它操作的是什么容器)
v.erase(remove(v.begin(),v.end(),99),v.end())
list的remove函数是个例外,它结合了remove和erase,因此会真正删除元素。
与remove类似的算法还有remove_if和unique。
第34条:了解哪些算法要求使用排序的区间作为参数
用于查找的算法binary_search、lower_bound、upper_bound、和equal_range要求排序的区间,因为它们使用二分法查找数据。它们对数时间的查找效率,但如果不是随机访问迭代器,则不能保证。
unique和unique_copy不要排序区间,因为它们只删除连续相等的元素只保留一个。
标准STL没有包含copy_if算法。
第37条:使用accumulate或者for_each进行区间统计
accumulate(v.begin(),v.end(),0.0);
accumulate(v.begin(),v.end(),1.0f,multiplies<float>());
Point avg=for_each(v.begin(),v.end(),PointAverage()).result();
for_each执行完毕后返回它的函数(在上一行的例子中会返回PointAverage(),因此结果可以访问它的result()成员函数)。
accumulate绝不仅仅是将几个数加在一起求合那么简单,可以自定义运算子。相比之下,for_each稍显繁琐但更灵活。原因在于 accumulate返回的是size_type,而for_each返回Function。
第38条:遵循按值传递的原则来设计函数子类
template<class InputIterator,class Function> Function //返回值类型为Function for_each(InputIterator first, InputIterator last, Function f); // 注意值传递
在STL中,函数对象在函数之间来回传递的时候也是像函数指针那样按值传递的。因此,你的函数对象必须尽可能的小,否则拷贝的开销会很大;其次,函数对象必须是单态的,也就是说,它们不得使用虚函数。这是因为,如果参数的类型是基类类型,而实参是派生类对象,那么在传递过程中会产生剥离问题(slicing problem):在对象拷贝过程中,派生部分可能会被去掉,而仅保留了基类部分(见第3条)。
试图禁止多态的函数子同样也是不实际的。所以必须找到一种两全其美的办法,既允许函数对象可以很大并且/或保留多态性,又可以与STL所采用的按值传递函数子的习惯保持一致。这个办法就是:将所需要的数据和虚函数从函数子中分离出来,放到一个新的类中,然后在函数子中设一个指针,指向这个新类。
第39条:确保判别式是“纯函数”
有几个名词可以来解释一下
■ 判别式(predicate)返回bool类型的函数
■ 纯函数(pure fuction)指返回值仅与输入有关
■ 判别式类 一个函数子类,它的operator()函数是一个判别式
问题根源在于:在一个算法中,你无法确定函数子被调用多少次。像for_each这类简单的算法调用次数就是迭代器的个数,但其它的较复杂的就不一定了,比如find_if就不一样。设计函数功能单一是一个重要的原则。
第40条:若一个类是函数子,则应使它可配接
总共有4个配接器not1, not2, bind1st, bind2nd(它们要求一些特殊的类型定义,argument_type、first_argument_type、second_argument_type、result_type)。如果你要使用它们,你必须确保函数子类从unary_function, binary_function等继承。或者使用ptr_fun, mem_fun等(这个用起来不太方便)。对于前一种方法,有一个使用特点
1、对于无状态函数子类(指没有私有成员变量的),通常定义为struct;这是STL的风格,当然用class也完全一样。
2、一般情况下,传递给unary_function或binary_function的非指针类型需要去掉const和引用(&)部分。(作者不愿意解释其中的原因,但我的编译器加上它们后也没错)
struct Cmp: public std::binary_function<int, int, bool> {
bool operator()(const int& i, const int& j);
}
如果是指针,就要写成一致
struct Cmp: public std::binary_function<const int*, const int*, bool> {
bool operator()(const int* i, const int* j);
}
其实写函数子也不是很麻烦
第41条:理解ptr_fun、mem_fun和mem_fun_ref的由来
用一句话来说就是“函数调用方式的多样性,使用STL必须规定一种方式来约束”。
C++三种不同的调用语法:f(x)、x.f()、p->f(),其中后两种是成员函数形式,而STL总是使用第一种,也就是非成员函数的语法形式。因此需要ptr_fun、mem_fun和mem_fun_ref进行配接。
其中,mem_fun用于指针容器,也就是将第三种转为第一种,它的具体声明为:
template<typename R,typename C> //C是类,R是所指向的成员函数的返回类型
mem_fun_t<R,C>
mem_fun(R(C::*pmf)());
因此调用时for_each(v.begin(),v.end(),mem_fun(&Widget::test)),for_each接收到一个类型为mem_fun_t的对象,该对象中保存了一个指向Widget::test的指针,对容器v中每一个Widget*指针,for_each使用语法1(非成员函数形式)调用mem_fun_t对象,然后该对象使用语法3调用Widget*指针的Widget::test()。
而mem_fun_ref用于对象容器。
ptr_fun的说明从另一个博客摘录如下:
ptr_fun是将一个普通的函数适配成一个仿函数(functor), 添加上argument_type和result type等类型,它的定义如下:
template<class _Arg1, class _Arg2, class _Result> inline pointer_to_binary_function<_Arg1, _Arg2, _Result, _Result(__clrcall *)(_Arg1, _Arg2)> ptr_fun(_Result (__clrcall *_Left)(_Arg1, _Arg2)) { // return pointer_to_binary_function functor adapter return (pointer_to_binary_function<_Arg1, _Arg2, _Result, _Result (__clrcall *)(_Arg1, _Arg2)>(_Left)); }
下面的例子就是说明了使用ptr_fun将普通函数(两个参数, 如果有多个参数, 要改用boost::bind)适配成bind1st或bind2nd能够使用的functor,否则对bind1st或bind2nd直接绑定普通函数,则编译出错。
#include <algorithm> #include <functional> #include <iostream> using namespace std; int sum(int arg1, int arg2) { std::cout<< "arg1 = " << arg1 << std::endl; std::cout<< "arg2 = " << arg2 << std::endl; int sum = arg1 + arg2; std::cout << "sum = " << sum << std::endl; return sum; } int main(int argc, char *argv[], char *env[]) { bind1st(ptr_fun(sum), 1)(2); // the same as sum(1,2) bind2nd(ptr_fun(sum), 1)(2); // the same as sum(2,1) return 0; }
第42条:确保less<T>与operator<具有相同的语义
在排序容器中,有一个默认的比较函数子less<T>,见第20条。确保比较函数子可行性是很重要的,同时,为了避免出现混乱,less<T> (如果需要自己实现,就不能用less这个名字)与operator<不同会有歧义。
第46条:考虑使用函数对象而不是函数作为STL算法的参数
C/C++不能真正将函数作为参数,而传递的函数指针,因此没法实现内联,而用函数对象作为参数,虽然有实例化然后调用operator()的开销,但可以实现内联(显式inline或者定义在类的内部即隐式内联),因此效率更高。例如C++的sort就性能而言优于C的qsort。