第一条:慎重选择容器类型
1、C++容器:先混个眼熟
序列容器:array、vector、string、deque、list、forward_list
有序关联容器:set、map、multiset、multimap
无序关联容器:unordered_set、unordered_map、unordered_multiset、unordered_multimap
容器适配器:stack、queue、priority_queue
2、C++容器:简介
2.1 序列容器
序列容器实现了可以顺序访问的数据结构。
array:静态连续容器;
vector:动态连续容器,超出容器大小会自动分配内存;
deque:双端队列;
list:双链表;
forward_list:单链表;
2.2 有序关联容器
关联容器实现可以快速搜索的排序数据结构(O(log n)复杂度)。
set:只有键,没有值的集合,按键排序,并且键是唯一的;
map:键值对的集合,按键排序,并且键是唯一的;
multiset:只有键,没有值的集合,按键排序,键可以不唯一;
multimap:键值对的集合,按键排序,键可以不唯一;
2.3 无序关联容器
无序关联容器实现了可以快速搜索的未排序(哈希)数据结构(O(1)最好,O(n)最坏情况的复杂性)。
unordered_set:只有键,没有值的集合,按键哈希散列,并且键是唯一的;
unordered_map:键值对的集合,按键哈希散列,并且键是唯一的;
unordered_multiset:只有键,没有值的集合,按键哈希散列,键可以不唯一;
unordered_multimap:键值对的集合,按键哈希散列,键可以不唯一;
2.4容器适配器
容器适配器为顺序容器提供了不同的接口。
stack:先进后出;
queue:先进先出;
priority_queue:优先级队列
3、选择容器
默认序列容器:vector,先考虑vector是否符合要求,如果不符合再选择其它的;
容器大小从始至终不会变,选择array:例如记录一年中每个月的收入;
需要在序列中间做插入和删除操作,选择list;
需要在序列头部、尾部做插入和删除操作时,选择deque;
第二条:不要试图编写独立于容器类型的代码
1、禁止对容器进一步泛化
每一种容器是针对一类场景的泛化,不要试图进一步泛化容器。比如:针对当下场景使用vector容器,写代码时就围绕vector接口来写,不要试图写出能够兼容list、deque的代码。虽然这么做出发点是好的,但是最终总是误入歧途。
2、建议封装容器到类中
要想减少再替换容器类型时所需要修改的代码,可以把容器隐藏到一个类中,并尽量减少与容器相关的外部可见的接口。
第三条:确保容器中的对象拷贝正确而高效
1、使用智能指针
使拷贝动作高效、正确,并防止剥离问题发生的一个简单办法是使容器包含指针而不是对象,最佳选择是使用智能指针。
第四条:调用empty而不是检查size()是否为0
理由很的简单:empty对所有的标准容器都是常数时间操作,而对一些list实现,size耗时线性增长。
第五条:区间成员函数优于与之对应的单元素成员函数
1、区间成员函数
区间成员函数像STL算法一样,使用两个迭代器参数来确定该成员操作所执行的区间。
2、区间成员函数的优点
代码量少、表达清晰直接,易写易懂。
3、何时使用区间成员函数
1)通过利用插入迭代器的方式来限定目标区间的copy调用,几乎都应该被替换为对区间成员函数的调用;
2)所有标准容器都提供了使用区间创建的构造函数;
3)所有标准容器都提供了使用区间插入的insert函数;
4)所有标准容器都提供了使用区间删除的erase函数;
5)所有标准容器都提供了使用区间赋值的assign函数
第六条:当心C++编译器最烦人的分析机制
请使用命名的迭代器对象,这消除二义性,维护代码的人更容易理解。
这里举例一个容易犯错的代码:将变量定义写成了函数声明。
第七条:如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉
STL容器很智能,但没有智能到知道是否该删除自己所包含的指针所指向的空间。为了避免内存泄漏,建议使用引用计数形式的智能指针对象。
第八条:切勿创建包含auto_ptr的容器对象
不多说了,很多年以前就不再使用auto_ptr了。
第九条:慎重选择删除元素的方法
1、删除特定值
对于连续容器,使用erase-remove:v.erase(remove(v.begin(), v.end(), 1987), c.end());
对于非连续容器list,使用remove函数;
对于关联容器,使用erase;
2、删除满足特定条件的对象
对于连续容器,使用erase-remove_if;
对于非连续容器,使用list::remove_if
对于关联容器,使用remove_copy_if和swap或者遍历使用erase,注意参数使用后缀递增;
第十条:了解分配子(allocator)的约定和限制
一个类型为T的对象,它的默认分配子allocator的两个类型定义分别是allocator::pointer和allocator::reference。
其他待完善,没有理解分配子的作用。
第十一条:理解自定义分配子的合理用法
第十二条:切勿对STL容器的线程安全性有不切实际的依赖
对容器成员函数的每次调用,都锁住容器直到调用结束;
在容器返回的每个迭代器的生存期结束前,都锁住容器;
第十三条:vector和string优先于动态分配的数组
不解释了,直接照做就是了。
第十四条:使用reserver来避免不必要的重新分配
reserver成员函数能使你把重新分配的次数减少到最低限度。
size:容器中有多少元素;注意:不等于容器容纳多少元素;
capacity:容器已经分配的内存可以容纳多少元素;
resize:强迫容器改变到包含n个元素的状态。
reserve:强迫容器把它的容器变为至少是n,如果n不小于当前的大小,会重新分配;如果小于,什么也不做。
第十五条:注意string实现的多样性
不同的string实现以不同的方式来组织下列信息:字符串的大小、容量capacity、字符串的值、对值的引用计数。
第十六条:了解如果把vector和string数据传给旧的API
vector返回C API:
vector<int> v;
if(!v.empty()){
doSomething(&v[0], v.size());
}
注意:不要用v.begin()代替&v[0]
string返回C API:s.c_str();
第十七条:使用“swap技巧”除去多余的容量
class Contestant{... ...};
vector<Contestant> contestants;
... ...
vector<Contestant>(contestants).swap(contestants);
string s;
... ...
string(s).swap(s);
第十八条:避免使用vector
首先,它不是一个STL容器;
其次,它并不存储bool
代替方法:
deque<bool>
或者使用bitset
第十九条:理解相等(equality)和等价(equivalence)的区别
相等:operator==
等价:!(x<y) && !(y<x),对于两个对象x和y,如果按照关联容器c的排列顺序,每个都不在另一个的前面,那么称这两个对象按照c的排列顺序是等价的。
第二十条:为包含指针的关联容器指定比较类型
如果什么也不做,默认是对指针的地址做排序。必须自己编写比较函数子类。
比较函数模板如下:
struct DereferenceLess {
tumplate<typename PtrType>
bool operator()(PtrType pT1, PtyType pT2) const
{
return *pT1 < *pT2;
}
}
第二十一条:总是让比较函数在等值情况下返回false
如果比较函数在等值情况下返回true,两个相等的值,可能不等价。
比如使用less_equal(operator<=)作为比较函数,x=y=10时,!(x<=y) && !(y<=x)的结果为false,即最终会得出:x!=y
第二十二条:切勿直接修改set或multiset的值
所有的标准关联容器是按照一定顺序来存放的,如果修改了会打破容器的有序性。
第二十三条:考虑用排序的vector替代关联容器
如果元素少并且几乎不会有插入删除操作,可以考虑使用排序的vector替代关联容器,无论是空间还是时间都是最优的。
第二十四条:当效率至关重要时,请在map::operator[]和map::insert之间谨慎做出选择
map::operator[]的设计目的是为了提供“添加和更新”的功能;
在作为“添加”操作时,insert比operator[]效率高;
当作为“更新”操作时,优先使用operator[]
第二十五条:熟悉非标准的散列表
非标准的散列表:hast_set、hast_multiset、hash_map、hash_multimap
注意这里说的是旧版本的STL
第二十六条:iterator优先于const_iterator、reverse_iterator及const_reverse_iterator。
原因:
1、容器中的instert和crase函数的形参只接受iterator类型,不接受const_iterator、reverse_iterator及const_reverse_iterator。
2、从iterator到const_iterator,或者从reverse_iterator到const_reverse_iterator之间都存在隐式转换,但是反过来技术上可以实现,但不推荐,效率不能保证。
3、在同一个表达式中混用iterator和const_iterator,比如比较操作,会发生隐式转换,有时会出现问题。
第二十七条:使用distance和advance将容器的const_iterator转换成iterator
distance:用于取得两个迭代器之间的距离;
advance:用于将一个迭代器移动指定的距离。
typedef deque<int> IntDeque;
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
...
Iter i(d.begin()); /迭代器 i 指向容器 d 起始位置
advance(i, distance<ConstIter>(i, ci)); /注意这里指明distance所使用的类型为ConstIter
效率问题:对于随机访问的迭代器(如vector、string、deque产生的迭代器)而言,执行时间是常数时间;对于其他标准容器以及散列表的迭代器而言,执行时间是一个线性时间,因此推荐二十六条,尽量用iterator代替const_iterator
第二十八条:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法
1、插入操作:如果要在一个迭代器reverse_iterator ri指定的位置上插入一个新元素,则只需在ri.base()位置处插入元素即可。
2、删除操作:如果要在一个迭代器reverse_iterator ri指定的位置上删除一个元素,要先递增ri,然后在调用base()函数,例如:v.erase((++ri).base());
第二十九条:对于逐个字符的输入请考虑使用istreambuf_iterator
对比istream_iterator,它在默认请看下会跳过空白字符,并且效率低。
读取一个文本文件的内容到一个string对象中,推荐的方案如下:
ifstream inputFile("test.txt");
string fileData((istreambuf_iterator<char>(inputFile)),
istreambuf_iterator<char>());
第三十条:确保目标区间足够大
对于vector、string、deque、list容器在尾部插入对象时,需要使用back_inserter函数;
其中deque和list容器还可以使用front_inserter在头部插入对象。
为了提供插入操作的性能,对于vector和string容器可以使用reserve来提前分配好内存。
第三十一条:了解各种与排序有关的选择
按照性能由高到底排序:
1、partition:把满足特定条件的元素放到前面;
2、stable_partition:把满足特定条件的元素放到前面,并且是稳定的排序(在遇到相等的对象时,还会按照出场顺序排序);
3、nth_element:找出部分最优的对象,可以不按照顺序,比如列出前十个,而且这十个可以不用排序;
4、partial_sort:部分排序;
5、sort:全部排序
6、stable_sort:稳定版本的全部排序,不仅全部排好序,而且在遇到相等的对象时,还会按照出场顺序排序。
注意:list::sort是稳定排序。
第三十二条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase
注意:remove不能删除容器中元素。
因为从容器中删除元素的唯一方法是调用该容器的成员函数,而remove并不知道它操作的元素所在的容器,所以remove不可能从容器中输出删除元素。
remove实际功能:把不用被删除的元素放到容器前部,把需要被删除的元素放到容器尾部。
如果需要真正删除元素,需要使用erase和remove配合
vector<int> v;
...
v.erase(remove(v.begin(), v.end(), 99), v.end());
同理unique也需要和erase配合使用。
注意:list::remove会真正删除元素,并且比使用erase-remove配合使用更高效,list::unique也会真正删除元素。
第三十三条:对包含指针的容器使用remove这一类算法时要特别小心
对指针容器使用erase-remove组合来删除时,很可能造成内存泄漏。
其实在执行remove后,还没有执行erase之前已经发生内存泄漏。因为remove在将不需要删除的指针元素移动到容器头部时,会覆盖掉需要删除的指针元素,造成内存泄漏;
再执行erase时,也会造成内存泄漏,因为还没释放内存,就删除了指针。
同理:remove_if和unique也会造成内存泄漏。
解决方法有两种:
1、使用带有引用计数的智能指针
2、使用partition代替remove
第三十四条:了解哪些算法要求使用排序的区间作为参数
需要排序后才能使用的算法:
// 使用二分法查找的算法,需要先排序
binary_search
lower_bound
upper_bound
equal_range
// 集合操作,为了提高效率(为了保证线性时间效率),需要先排序
set_union
set_intersection
set_defference
set_symmetric_defference
// 合并操作,为了提高效率
merge
inplace_merge
// 判断一个区间中的所有的对象是否都在另一个区间中,为了提高效率
includes
第三十五条:通过mismatch或lexicographical_compace实现简单的忽略大小写的字符串比较
略
第三十六条:理解copy_if算法的正确实现
STL中包含copy的算法
copy
copy_backward
replace_copy
replace_copy_if
reverse_copy
unique_copy
remove_copy
remove_copy_if
rotate_copy
partial_sort_copy
uninitialized_copy
但是就是没有copy_if算法,需要自己实现
template<typename InputIterator,
typename OutputIterator
typename Predicate>
OutputIterator copy_if(InputIterator begin,
InputIterator end,
OutputIterator destBegin,
Predicate p)
{
while (begin != end) {
if (p(*begin))
*destBegin++ = *begin;
++begin;
}
return destBegin;
}
第三十七条:使用accumulate或者for_each进行区间统计
count:统计一个区间中有多少个元素;
count_if:统计满足某个条件的元素个数;
min_element:获取区间中最小值;
max_element:获取区间中最大值;
accumulate:对区间进行自定义的操作,比如计算一个容器中字符串长度的总和。
/计算和
list<double> ld;
...
double sum = accumulate(ld.begin(), ld.end(), 0.0);
/ 计算容器中字符串长度的总和
string::size_type
stringLengthSum(string::size_type sumSoFar ,
const string& s)
{
return sumSoFar + s.size();
}
set<string> ss;
...//插入一些字符串
string::size_type lengthSum =
accumulate(ss.begin(), ss.end(),
static_cast<string::size_type>(0),
stringLengthSum);
};
第三十八:遵循按值传递的原则来设计函数子类
1、在C和C++的标准库函数中,如果需要函数作为参数,需要使用函数指针,并且函数指针是按值传递的(被复制);
2、在STL中,函数对象在函数之间来回传递也是按值传递(被复制);
3、因为函数对象是按值传递,即需要来回复制,因此函数对象要尽可能的小;
4、函数对象必须是单态的(不能是多态),也就是说,它们不能有虚函数,因此在传递过程中,会出现剥离问题(slicing problem):在对象复制过程中,派生部分可能会被去掉,而仅保留了基类部分。
第三十九:确保判别式是“纯函数”。
1、判别式函数(predicate function):是一个返回值为bool类型(或者可以隐式地转换为bool类型)的函数。
2、判别式类(predicate class):是一个函数类,它的operator()函数是一个判别式。
3、纯函数(pure function):是指返回值仅仅依赖于其参数的函数。
4、在STL中容器使用的比较函数都是判别式,比如:find_if以及各种与排序相关的算法,这些算法要求每执行一次都要保证结果是唯一,不会受其他参数的干扰,比如全局变量、静态变量等。
第四十条:若一个类是函数类,则应使它可配接
1、STL函数配接器not1、not2、bind1st、bind2nd等都要求一些特殊的类型定义,提供这些必要的类型定义的函数对象被称为可配接的(adaptable)函数对象。
2、特殊的类型定义:argument_type、first_argument_type、second_argument_type、result_type
3、继承 std::unary_function、std::binary_function来完成上述的特殊的类型定义
第四十一条:理解ptr_fun、mem_fun和mem_fun_ref的来由
STL算法只支持非成员函数,不支持成员函数(无法通过编译),如果要使用成员函数需要使用ptr_fun、mem_fun或者mem_fun_ref来将成员函数封装到一个函数类中
第四十二条:确保less与operator<具有相同的语义
1、不要特化一个位于std名字空间中的模板
2、operator<是std::less默认实现方式,不要修改std::less的行为,因为这样做很可能会误导其它的程序员。
第四十三条:算法调用优先于手写的循环
1、先看一个手写循环和使用算法的例子
一个支持重画的类Widget
class Widget{
public:
...
void redraw() const;
...
}
使用手写循环来重画容器list中的所有的Widget:
list<Widget> lw;
...
for (list<Widget>::iterator i=iw.begin(); i!=iw.end(); ++i){
i->redraw();
}
使用for_each循环算法来完成
for_each(lw.begin(), lw.end()),
mem_fun_ref(&Widget::redray));
mem_fun_ref:封装成员函数,使它可以用在算法中,因为算法只能使用非成员函数
2、使用算法的优点
效率高、可维护性好。
理由:略,一定要用起来
第四十四条:容器的成员函数优先于同名的算法
1、列举出这些函数
关联容器:count、find、lower_bound、upper_bound、equal_range
list容器:remove、remove_if、unique、sort、merge、reverse
2、理由
速度更快、成员函数通常与容器结合的更加紧密
第四十五条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range
1、如果容器的区间是排序好的使用:binary_search、lower_bound、upper_bound和equal_range,
这些算法是对数时间的效率。
2、如果容器的区间不是排序好的使用:count、count_if、find、find_if,
这些算法是线性时间的效率。
count:区间中是否存在某个特定的值?如果存在的话,有多少个拷贝?
find:区间中有这样的值吗?如果有,它在哪里?
两者还有一个区别:find找到后就返回,效率高;count要遍历一遍容器的区间
binary_search:测试一个排序的容器区间中是否存在某一个特定的值,返回true或者false;
equal_range:查找一个值在容器区间中的位置;
lower_bound:查找一个值在容器区间中第一次出现的位置,如果没有,则返回适合插入该值的位置;
第四十六条:考虑使用函数对象而不是函数作为STL算法的参数
使用函数对象比直接使用函数作为STL算法的参数,更高效。
原因是:编译器可以对函数对象做内敛优化,而函数作为参数,其实是指针,编译器不会对指针做优化。
第四十七条:避免产生“直写型”(wirte-only)的代码
1、不要写过于复杂的嵌套函数调用
2、注意添加代码的注释
第四十八条:总是包含(#include)正确的头文件
1、有些标准头文件可以省略,也可以通过编译,但是考虑移植性,强烈建议不要省略;
2、标准的STL容器都被声明在与之同名的头文件中:vector、list等,特殊的:multiset和set都在<set>中,map和multimap都在<map>中
3、算法被声明在<algorithm>中,除了下面四个
4、accumulate、inner_product、adjacent_difference、partial_sum被声明在<numeric>中
5、特殊类型的迭代器被声明在<iterator>中
6、标准的函数类和函数配接器被声明在<functional>中:如not1、bind2nd
第四十九条:学会分析与STL相关的编译器诊断信息
1、std::basic_string<… …>很长的一个信息,翻译成string就好;
2、vector和string的迭代器通常就是指针,所以错误的使用iterator错误信息中会有:double *
3、如果错误消息源于某一个STL算法的内部实现,那么也许在调用算法的时候,使用了错误的类型。
第五十条:熟悉与STL相关的web站点
SGI站点:www.sgi.com/tech/stl/
STLport站点:www.stlport.org
Boost:www.boost.org