参考链接:http://blog.csdn.net/haotiangg/article/details/76266579
第一章 容器
第1条:慎重选择容器类型:选择合适的容器很关键!
C++中各种标准或非标容器:
标准STL序列容器: vector、string、deque和list(双向列表)。
标准STL关联容器: set、multiset、map和multimap。
非标准STL序列容器: slist(单向列表)和rope(重型字符串?)。
非标准STL关联容器: hash_set、hash_multiset、hash_map和hash_multimap。(c++11引入了unordered_set、unordered_multiset、unordered_map和unordered_multimap,其亦基于hash表,但属于最新的标准关联容器,所以相对hash_*拥有更高的效率和更好的安全性)
标准非STL容器: 数组(c++11中有新的array标准)、bitset、valarray(用于数值计算,但是一般很少使用)、stack、queue和priority_queue(常用于模拟最大堆和最小堆)
其他: vector
不要使用 vector
选择参考:
面对不同的情形,选择合适的容器!
第二条:小心对“容器无关代码”(container-independent code)的幻想。
本条款要告诫程序员:编写与容器无关的代码是没有必要的。
STL是建立在泛型的基础上,但由于不同容器的特性不同(尤其是迭代器、指针和引用的类型与失效规则不同),支持所有容器的相同接口是不存在的。比如:
只有序列容器支持: push_front和push_back,
只有关联容器支持: logN时间复杂度的lower_bound、upper_bound和equal_range;
如果在工程中需要跟换容器类型,可以通过用typedef来减少跟换所带来的代码跟新量。
class Widget { ... };
typedef vector WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
...
WCIterator i = find(cw.begin(), cw.end(), bestWidget);
同样,typedef可以用来简化一个经常被运用到的容器的定义,并减少维护的成本。但是typedef只是其他类型的同义字,第三条:确保容器中的对象拷贝正确而高效:
容器容纳了对象,但不是你给它们的那个对象。当你向容器中插入一个对象时,你插入的是该对象的拷贝而不是它本身;当你从容器中获取一个对象时,你获取的是容器中对象的拷贝。
拷贝对象是STL的基本工作方式。当你删除或者插入某个对象时,现有容器中的元素会移动(拷贝);当你使用了排序算法,remove、uniquer或者他们的同类,rotate或者reverse,对象会移动(拷贝)。
一个使拷贝更高效、正确的方式是建立指针的容器而不是对象的容器,即保存对象的指针而不是对象,然而,指针的容器有它们自己STL相关的头疼问题,改进的方法是采用智能指针。
第四条:调用empty而不是检查size()是否为0
对于任意容器c,写下
if (c.size() == 0)…
本质上等价于写下
if (c.empty())…
但是为什么第一种方式比第二种优呢?理由很简单:对于所有的标准容器,empty是一个常数时间的操作,但对于一些list实现,size花费线性时间。
这什么造成list这么麻烦?为什么不能也提供一个常数时间的size?如果size是一个常数时间操作,当进行增加/删除操作时每个list成员函数必须更新list的大小,也包括了splice,这会造成splice的效率降低(现在的splice是常量级的),反之,如果splice不必修改list大小,那么它就是常量级地,而size则变为线性复杂度,因此,设计者需要权衡这两个操作的算法:一个或者另一个可以是常数时间操作。
第五条:尽量使用区间成员函数代替单元素操作
给定两个vector,v1和v2,怎样使v1的内容和v2的后半部分一样?
可行的解决方案有:
(1)使用区间函数assign:
v1.assign(v2.begin() + v2.size() / 2, v2.end());
(2)使用单元素操作:
vector::const_iterator ci = v2.begin() + v2.size() / 2;
ci != v2.end();
++ci)
v1.push_back(*ci);
v1.clear();
copy(v2.begin() + v2.size() / 2, v2.end(), back_inserter(v1));
v1.insert(v1.end(), v2.begin() + v2.size() / 2, v2.end());
首先,使用区间函数的好处是:
● 一般来说使用区间成员函数可以输入更少的代码。
● 区间成员函数会导致代码更清晰更直接了当。
使用copy区间函数存在的问题是:
【1】 需要编写更多的代码,比如:v1.clear(),这个与insert区间函数类似
【2】 copy没有表现出循环,但是在copy中的确存在一个循环,这会降低性能
单元素变量操作指运用循环,将单个元素一一进行的操作,这类操作主要是插入(insert,push_front,push_back)和删除操作(erase)。值得注意的是如下copy操作虽然看起来是区间操作,其本质上还是单元素操作。
使用insert单元素版本的代码对你征收了三种不同的性能税,分别为:
【1】 没有必要的函数调用;
【2】 无效率地把v中的现有元素移动到它们最终插入后的位置的开销;
【3】 重复使用单元素插入而不是一个区间插入必须处理内存分配。
使用区间操作的好处是什么呢?以insert函数为例:一、减少函数调用,区间操作只需调用一次insert(),而单元素操作需要调用N次insert();二、对于内存连续型容器,每次插入时,需要移动后面的所有元素。所以区间操作只需移动一次,而单元素操作需要移动N次(list型不需要移动元素,但需要设置prev和next指针,也会造成类似的效率影响);三、对于vector和string,当所分配内存已满,需要重新分配内存并移动容器内已有元素。区间操作会实现计算所需内存,只需最多一次的内存重新分配和元素移动,而但单元素操作需要logN次内存重新分配和元素移动。
下面进行总结:
说明:参数类型iterator表示容器的迭代器类型,也就是container::iterator,参数类型InputIterator表示可以接受任何输入迭代器。
【1】区间构造
所有标准容器都提供这种形式的构造函数:
container::container(InputIterator begin, // 区间的起点
InputIterator end); // 区间的终点
【2】区间插入void container::insert(iterator position, // 区间插入的位置
InputIterator begin, // 插入区间的起点
InputIterator end); // 插入区间的终点
void container::insert(lnputIterator begin, InputIterator end);
iterator container::erase(iterator begin, iterator end);
void container::erase(iterator begin, iterator end);
【4】区间赋值
所有标准列容器都提供了区间形式的assign:
void container::assign(InputIterator begin, InputIterator end);