——STL的优点就不罗嗦了,对一个C++开发人员来讲,不熟悉STL是无论如何都说不过去的,这不需要什么借口。侯捷老师的大作《STL源码剖析》确实是深入学习STL的绝佳教材,于是决定好好研读一下这本书,并将所学记录于blog上,作为一个学习笔记,说不定还能遇到不少同道中人共同学习。
如果能更多的了解STL的机制,就可以不仅更加纯熟的应用,掌握一个开发的利器,更能学习其设计思想而加以应用,使自己的功力更进一层。
一 STL简简史
C++由Bjarne Stroustrup创立于1979年,而就在那一年Alexander Stepanov创立了STL,大师的眼界总是这么超前哪;直到1993年9月STL才开始了进入C++标准库的历程,并于1998年9月STL终于正式进入C++标准,成为C++程式库的一大派系。就连C++程式库原有的stream和string也都用template重新写过,可见其影响力(经常看到网上对string的指责,应该和后入STL也有点关系吧)。
STL有不同的版本,包括HP的版本(所有STL之源),P.J.Plauger版本(Visual C++采用的就是这个),Rouge Wave版本(C++ Builder采用)和SGI版本。侯捷老师推荐的是SGI的版本,可读性更高,《STL源码剖析》也是以其为蓝本。
二 STL 六大组件
学习使用STL是相对简单的,然而如果要深入STL的底层,那里却是海底的冰山,其复杂性是不言而喻的,哦,希望不要把人吓倒,好消息是:这也是一座充满惊喜的宝库。
先来看看这六大组件吧,这里先有个基本的概念,等用到它们的时候再详细探究到底是怎么回事吧。
1. 容器(containers),STL里面有很多容器,比如list, vector, deque, ma
p,这都是比较常用的,容器是用来组织存放数据的。你甚至可以把int A[20]声明的数组看作是一个整型容器,它以线性相关的方式存储了20个整型数据。STL的容器是一种class template。
2. 演算法(algorithms),比如sort, search等各种常用的算法,STL中的演算法相当于function template。
3. 迭代器(iterators), 有了容器就需要知道如何访问容器里面的元素,就像一个数组可以使用索引访问一样,迭代器就是访问容器的接口,而又不会出卖容器内部的数据组织方式。所有的STL容器都必须附带自己的迭代器——毕竟只有容器设计者自己才知道如何访问容器的元素。
4. 仿函式(functors),防函式是一种重载了operator()的class或者class template,容易让人感到困惑的东西。
5. 适配器(adapters),一种包装了容器或者防函式的东西,只是根据需要提供必须的接口,而不是把提供原来的所有接口,想想DesignPattern中的Adapter模式吧,是一样的了。比如STL中的queue和stack虽然很像容器,但是实际上只是适配器,因为其底层完全重用了deque。
6. 配置器(allocators),负责空间分配管理,在STL中配置器也是一个class。将配置器独立成一个组件的好处就是,你可以根据需要定制自己的配置器,这样既可以利用S
TL的便利性,又可以满足效率的要求。
图1-1显示了六大组件之间的相互关系。
《STL源码解析》中还讲了STL中的各种常数定义,并用小程序测试了各平台对C++的支持度,在1.8和1.9.1节。
图1-1 六大组件之间的互动关系【直接截自《STL源码剖析》】
Container通过Allocator获得存储空间;Algorithm通过Iterator存取Container的内容;
Adaptor可以套接Functor;Functor协助Algorithm完成不同的策略变化。
1.9节还讲到了C++一些容易令人困惑的语法,临时对象(在下面会有提及);静态整型常量(int, long, char, short等整型类型)在class内的直接初始化;STL的前闭后开区间(数据始于begin而终结于last-1位置)等,这里只重复一下防函式的一些内容。
三 Function call运算子(operator () )速写
函数调用算子(C++语法中的())也可以被重载,如果一个类重载了(),它就成了一个仿函式。
在C语言中,如果需要就将一个函数作为参数传递,只能通过指针的方式,比如常用的qsort(函数:
int cmp(const void *a, const void*b){ … }
A a[20];
…
qsort(a, 20, sizeof(A), cmp);
而在STL中,这些是通过防函式完成的,防函式具有可适配性(adaptability)——也就是可以将某些条件加于其上而改变其状态,这是函数指针不具有的,和上面对应的例子如下:
template<class T>
class CCmp{
public: // 重载了(),类CCmp就成了一个防函式
int operator()(const T &a, const T &b){ … }
};
// 声明防函式对象
CCmp<A> cmpA;
// 使用防函式就像使用函数一样
if(cmpA(a0, a1) < 0){
cout<<”a0 < a1”<<endl;
}
// 还可以生成防函式的临时对象,同时使用
if(CCmp<A>()(a0, a1) < 0){
cout<<”a0 < a1”<<endl;
}
CCmp<T>已经很接近STL的防函式了,唯一缺乏的就是上面提到的可适配性。
今天暂时到这里吧,虽然磕磕绊绊也算可以成文了,明天继续。