1、慎重选择容器类型
a.标准STL序列容器:vector、string、deque、list
b.标准STL关联容器:set、 multiset、 map、 multimap
c.非标准序列容器:slist、 rope
d.非标准关联容器:hash_set、 hash_multiset、 hash_map、 hash_multimap
e.vector<char> 作为string的替代
f.vector作为标准关联式容器的替代。
g.几种标准的非STL容器:数组、bitset、valarray、 stack、 queue、 priority_queue。
vector、list和 deque的选择:vector是默认应使用的序列类型;当需要频繁在序列的中间做插入和删除操作时,应使用list; 当大多数插入和删除操作发生在序列的头部和尾部时,deque是应考虑的数据结构。
2、不要试图编写独立于容器类型的代码
因为每个容器都有自己独特的使用方式。可以将这些容器封装在类里使用,减少外界对容器特性的依赖。
3、确保容器中的对象副本正确而高效
容器保存的对象是你提供给容器的对象的副本,当从容器中取出一个对象时,得到的也是该对象的副本。进去的是副本,出来的也是副本。
当对象的复制操作非常复杂时,导致程序的性能的下降,而且如果容器中存放的是基类对象,当将派生类对象复制给基类对象时,发生剥离。 使复制动作高效、正确,并防止剥离问题发生的一个简单办法是使容器包含指针而不是对象。
4、调用empty()而不是检查size()是否为0
empty通常被实现为内联函数,并且它所做的仅仅是返回size是否为0。empty对所有的标准容器都是常数时间,而对于一些list实现,size花费线性时间。
5、区间成员函数优先于与之对应的单元素成员函数
区间成员函数使用两个迭代器参数来确定该成员操作所执行的区间。
区间成员函数写起来更容易,更能清楚地表达你的意图,而且它们表现出了更高的效率。
6、当心C++编译器最烦人的分析机制
class Widget{...}; Widget w();得到的不是Widget类型的对象,而是声明了一个函数w,返回值是WIdget类型的。STL也有类似的误用。
7、如果容器中包含了通过new操作创建的指针,切记在容器对象析构钱将指针delete掉。
使用引用计数形式的智能指针shared_ptr是一个很好的选择,它能保证释放分配的内存,即便是发生了异常。
8、切勿创建包含auto_ptr的容器对象。
auto_ptr的容器是被禁止的。因为当复制一个auto_ptr时,它所指向的对象的所有权被移交到考入的auto_ptr上,而它自身被设置为NULL。
9、慎重选择删除元素的方法。
10、了解分配子(allocator)的约定和限制
11、理解自定义分配子的合理用法
12、切勿对STL容器的线程安全性有不切实际的依赖
对容器成员函数的每次调用,都锁住容器直到调用结束;
在容器所返回的每个迭代器的生存期结束前,都锁住容器;
对于作用于容器的每个算法,都锁住该容器,直到算法结束。
最好把互斥器封装起来Lock的构造函数加锁,析构函数解锁,这样在发生异常时保证可以解锁。
第二章 vector和string
13、vector 和 string 优先于动态分配的数组。
如果使用new来动态分配内存,你要承担的责任:
必须确保以后会有人用delete来删除所分配的内存;
必须确保使用了正确的delete形式;
必须确保只delete了一次。
14、使用reserve来避免不必要的重新分配
reserve(Container::size_type n)强迫容器把它的容量变为至少是n,前提是n不小于当前的大小。
当一个元素需要被插入而容器的容量不够时,就会发生重新分配过程(包括原始内存的分配和释放,对象的复制和析构,迭代器、指针、引用的失效)。 避免重新分配的关键在于,尽早使用reserve,把容器的容量设为足够大。
15、注意string实现的多样性
16、了解如何把vector 和 string 数据传给旧的API
vector v; 得到一个指向v中数据的指针,应用的形式是 &v[0];
string s;对应的形式是 s.c_str();
17、使用swap技巧出去多余的容量
vector<int> v; .....; v<int>().swap(v);
string s; .....; string().swap(s);
18、避免使用vector<bool>
vector<bool>不是STL容器。每个bool仅占一个二进制位,有一个6位的字节容纳8个bool。
可以用deque<bool>替代,也可以用bitset替代。
第三章 关联容器
19、理解相等和等价的区别
相等是以operator==为基础的。
等价是以operator<为基础的。对于两个对象x,y,如果按照关联容器c的排列顺序,每一个都不在另一个的前面,即 x < y 为假, y < x 为假,那么称这两个对象按照c的排列顺序有等价的值。
20、为包含指针的关联容器指定比较类型
默认情况下关联容器的比较函数对象对指针的操作是指针值的比较,而不是指针所指对象的比较,因为容器中存储的就是指针的值。因此一般要编写自己的比较函数对象,自定义比较方法。
struct DereferenceLess
{
template<typename PtrType>
bool operator() (PtrType pT1, PtrType pT2) const
{
return *pT1 < *pT2;
}
};
};
21、总是让比较函数在等值情况下返回false
22、切记直接修改set或multiset中的键
修改键值将会打破容器的有序性。
23、考虑用排序的vector替代关联容器
标准关联容器使用的是红黑树作为底部结构实现的。这就对每个元素对象带来了额外的开销:指向左孩子的指针、指向右孩子的指针,一般还有指向父节点的指针。当数据量大时,会导致容器分配更多的页,在使用时要不断的发生页错误而导致页的换入换出,使得系统显著变慢。
而对于排序的vector容器,每个元素对象没有额外开销,末尾多余的空闲空间也可以去掉。这样比关联容器在存储相同数量的元素时,分配的页就较少,发生页错误的概率也就减少。
但是必须保证vector是排序的,而且一般只用于查找操作,而用于插入和删除将导致效率的下降。
24、当效率至关重要时,请在map::operator[] 与 map::insert之间慎重做出选择
如果要更新一个已有的映射表元素,则应优先选择operator[]; 但是如果要添加一个新元素,那么最好是选择insert,因为operator[]要首先创建一个默认的对象,然后给该对象进行赋值操作,这就导致了三个函数的调用:默认构造函数、析构函数、赋值操作函数。
25、熟悉非标准的散列容器。
hash_set、 hash_multiset、 hash_map、 hash_multimap
元素是未排序的,查找时间为O(1)
第4章 迭代器
26、iterator优先于const_iterator、 reverse_iterator以及const_reverse_iterator
减少混用不同类型的迭代器,尽量使用iterator 替代 const_iterator
27、使用distance 和 advance 将容器的const_iterator 转换成 iterator
iterator 和 const_iterator 之间不存在类型转换,隐式和显示都不行。
deque<int> d;
deque<int>::iterator iter;
deque<int>::const_iterator citer;
.....
iter = d.begin();
advance(iter, distance< deque<int>::const_iterator >(iter, citer));
28、正确理解由reverse_iterator 的 base()成员函数所产生的iterator用法。
29、对于逐个字符的输入请考虑使用istreambuf_iterator。
第5章 算法
30、确保目标区间足够大
要在算法执行过程中增大目标区间,请使用插入型迭代器,比如ostream_iterator,或者由back_inserter、 front_inserter、 inserter返回的迭代器。
31、了解与各种排序有关的选择
sort、 partial_sort(部分排序)、 nth_element(类似于快速排序的寻找枢轴位置的函数)
a.如果需要对vector、string、deque或者数组中的元素执行一次完全排序,那么可以使用sort或者stable_sort。
b.如果有一个vector、string、deque或者数组,并且只需要对等价性最前面的n个元素进行排序,那么可以使用partial_sort
c.
如果有一个vector、string、deque或者数组,并且需要找到第n个位置上的元素,或者,需要找到等价性最前面的n个元素但是又不必对这n个元素进行排序,那么nth_element正是所需要的函数。
d.如果需要将一个标准序列容器中的元素按照是否满足某个特定的条件区分开来,那么partition和stable_partition是所需要的。
32、如果确实需要删除元素,则需要在remove这一类算法之后调用erase。
从容器中删除元素的唯一方法是调用该容器的成员函数,而remove并不知道它操作的元素所在的容器,所以remove不可能从容器中删除元素。
33、对包含指针的容器使用remove这一类算法时要特别小心
为了防止内存泄露,最好用智能指针
34、了解哪些算法要求使用排序的区间作为参数。
binary_search、 lower_bound、 upper_bound equal_range
set_union set_intersection set_difference
merge includes
35、通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较。
36、理解copy_if算法的正确实现
37、使用accumulate 或者 for_each 进行区间统计
第6章 函数子、函数子类、函数及其他
38、遵循按值传递的原则来设计函数子类
如果一个函数对象包含了较多的数据成员或者 含有虚函数,则将这些数据成员和虚函数封装到一个单独的类中,在函数对象中用一个指针指向这个单独类对象。
39、确保判别式是纯函数
40、若一个类是函数子,则应使它可配接
41、理解ptr_fun、mem_fun和mem_fun_ref的由来
42、确保less<T> 与 operator<具有相同的语义
第7章 在程序中使用STL
43、算法调用优先于手写的循环
使用算法可以提高:效率、正确性、可维护性
44、容器的成员函数优先于同名的算法。
45、正确区分count、 find、 binary_search、 lower_bound、 upper_bound和equal_range
46、考虑使用函数对象而不是函数作为STL算法的参数
47、避免产生直写型(write-only)的代码
48、总是包含正确的头文件
<vector> 包含了 <string>
<algorithm> 包含了 <string>
<iostream> 包含了 <string> 和 <vector>
<string> 包含了 <algorithm>
<set> 包含了 <functional>
标准的函数子(比如less<T>)和函数配接器被声明在头文件<functional>中。
49、学会分析与STL相关的编译器诊断信息。
50、熟悉与STL相关的Web站点。
SGI STL 站点:http://www.sgi.com/tech/stl/
STLport 站点:
Boost Web 站点