从大学时就开始学习C++,到如今近5年的时间了却不多用到STL。如今想一想真得是对不起这门语言,也对不起宝贵的五年光阴。我钟爱C++,因此必定要彻底搞懂它,理解它。爱一我的的前提是要懂他(她),爱一门语言也是这样。郑重地向C++说声“对不起!”。我会把不懂你的方面慢慢弥补,作到真正懂你。为了更好地学习STL,我采用边学习,边总结,边写博客的方法,但愿可以造成一个学习专栏。这样既能够便于本身随时翻阅,又能够分享给有须要的人。固然在博客中,我有可能会引用到其它大牛博友的文章。为了尊重原创,我会给出参考博文的连接地址。另外,若是你们在文章中发现错误,但愿在评论下方给出提示建议。算法
Now,开始学习C++中重要的标准库STL。编程
STL的原名是“Standard Template Library”,翻译过来就是标准模板库。STL是C++标准库的一个重要组成部分,主要由六大组件构成。这六大组件是:
数组
容器(Container)、算法(algorithm)、迭代器(iterator)、仿函数(functor)、适配器(adapter)、配置器(allocator)。安全
容器能够分为三类即序列容器、关联容器和容器适配器。各种具体包含以下所示:数据结构
序列容器:vector、list、dequeapp
关联容器:set、map、multiset、multimapless
适配器容器:stack、queue、priority_queue数据结构和算法
容器 | 特性 | 所在头文件 |
向量vector | 在常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具备常数时间复杂度。对任意项的插入和删除的时间复杂度与到末尾的距离成正比,尤为对向量头的添加和删除代价比较高。 | <vector> |
双端队列deque | 基本上与向量相同,不一样点是,双端队列的两端插入和删除操做也是常量的时间复杂度。 | <deque> |
表list | 对任意元素的访问与两端的距离成正比,但对某个位置上插入和删除时间复杂度为常数。 | <list> |
队列queue | 插入只能够在尾部进行,删除、检索和修改只容许从头部进行。遵循FIFO的原则。 | <queue> |
栈stack | LIFO:先进后出原则。只容许在序列头部进行插入、删除、检索和修改操做,时间复杂度为常数。 | <stack> |
集合set | 内部实现机制是红黑树,每一个结点都包含一个元素,结点之间以某种做用于元素对的谓词排列,没有两个不一样的元素可以拥有相同的次序,具备快速查找的功能。 | <set> |
多重集合multiset | 和集合基本相同,但能够支持重复元素。 | <set> |
映射map | 由(键,值)对组成的集合,以某种做用于键对上的谓词排序。具备快速查找特性。 | <map> |
多重映射multimap | 支持一键对应多个值的特性,具备快速查找功能。 | <map> |
算法部分主要在头文件<algorithm>,<numeric>,<functional>中。<algoritm>是全部STL头文件中最大的一个,它是由一大堆模版函数组成的,能够认为每一个函数在很大程度上都是独立的,其中经常使用到的功能范 围涉及到比较、交换、查找、遍历操做、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操做。<functional>中则定义了一些模板类,用以声明函数对象。函数
迭代器是用类模板(class template)实现的.重载了* ,-> ,++ ,-- 等运算符。工具
迭代器分5种:输入迭代器、输出迭代器、 前面迭代器、双向迭代器、 随机访问迭代器。
输入迭代器:向前读(只容许读);
输出迭代器:向前写(只容许写);
前向迭代器:向前读写;
双向迭代器:向先后读写;
随机迭代器:随机读写;
仿函数用类模板实现,重载了符号"()"。仿函数,又或叫作函数对象,是STL六大组件之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎全部的算法都有仿函数版本。
例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素相等就找到了,可是什么是相等在不一样状况下却须要不一样的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法自己却不须要改变,这都多亏了仿函数。仿函数(functor)又称之为函数对象(function object),其实就是重载了()操做符的struct,没有什么特别的地方。
如如下代码定义了一个二元判断式functor:
struct IntLess { bool operator()(int left, int right) const { return (left < right); } };
仿函数的优点:
1)仿函数比通常函数灵活。
2)仿函数有类型识别。能够用做模板参数。
3)执行速度上仿函数比函数和指针要更快。
在STL里仿函数最经常使用的就是做为函数的参数,或者模板的参数。
在STL里有本身预约义的仿函数,好比全部的运算符=,-,*,、好比'<'号的仿函数是less。
// TEMPLATE STRUCT less template<class _Ty = void> struct less : public binary_function<_Ty, _Ty, bool> { // functor for operator< bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator< to operands return (_Left < _Right); } };
less继承binary_function<_Ty,_Ty,bool>
template<class _Arg1, class _Arg2, class _Result> struct binary_function { // base class for binary functions typedef _Arg1 first_argument_type; typedef _Arg2 second_argument_type; typedef _Result result_type; };
从定义中能够知道binary_function知识作了一些类型的声明,这样作就是为了方便安全,提升可复用性。
按照这个规则,咱们也能够自定义仿函数:
template <typename type1,typename type2> class func_equal :public binary_function<type1,type2,bool> { inline bool operator()(type1 t1,type2 t2) const//这里的const不能少 { return t1 == t2;//固然这里要overload== } }
之因此const关键字修饰函数,是由于const对象只能访问const修饰的函数。若是一个const对象想使用重载的()函数,编译过程就会报错。
小结一下:仿函数就是重载()的class,而且重载函数要有const修饰。自定义仿函数必需要继承binary_function(二元函数)或者unary_function(一元函数)。其中unary_function的定义以下:
struct unary_function { typedef _A argument_type; typedef _R result_type; };
适配器是用来修改其余组件接口的STL组件,是带有一个参数的类模板(这个参数是操做的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。
1)容器适配器:栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就能够被实现为基本容器类型(vector,dequeue,list)的适配。能够把stack看做是某种特殊的vctor,deque或者list容器,只是其操做仍然受到stack自己属性的限制。queue和priority_queue与之相似。容器适配器的接口更为简单,只是受限比通常容器要多。
2)迭代器适配器:修改成某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。
3)函数适配器:经过转换或者修改其余函数对象使其功能获得扩展。这一类适配器有否认器(至关于"非"操做)、绑定器、函数指针适配器。函数对象适配器的做用就是使函数转化为函数对象,或是将多参数的函数对象转化为少参数的函数对象。
例如:
在STL程序里,有的算法须要一个一元函数做参数,就能够用一个适配器把一个二元函数和一个数值,绑在一块儿做为一个一元函数传给算法。
find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42));
这句话就是找coll中第一个大于42的元素。
greater <int>(),其实就是">"号,是一个2元函数
bind2nd的两个参数,要求一个是2元函数,一个是数值,结果是一个1元函数。
bind2nd就是个函数适配器。
STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提高。
SGI-STL的空间配置器有2种,一种仅仅对c语言的malloc和free进行了简单的封装,而另外一个设计到小块内存的管理等,运用了内存池技术等。在SGI-STL中默认的空间配置器是第二级的配置器。
SGI使用时std::alloc做为默认的配置器。
template<class T, class Alloc> class simple_alloc { public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T)); } static T* allocate(void) { return (T*) Alloc::allocate(sizeof(T)); } static void deallocate(T* p, size_t n) { if (0 != n) Alloc::deallocate(p, n * sizeof(T)); } static void deallocate(T* p) { Alloc::deallocate(p, sizeof(T)); } }
内存的基本处理工具,均具备commit或rollback能力。
template<class InputIterator, class ForwardIterator> ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result); template<class ForwardIterator, class T> void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x); template<class ForwardIterator, class Size, class T> ForwardIterator uninitialized_fill_n(ForwardIterator first, ForwardIterator last, const T& x)
泛型技术的实现方法有:模板、多态等。模板是编译时决定的,多态是运行时决定的,RTTI也是运行时肯定的。
多态是依靠虚表在运行时查表实现的。好比一个类拥有虚方法,那么这个类的实例的内存起始地址就是虚表地址,能够把内存起始地址强制转换成int*,取得虚表,而后(int*)*(int*)取得虚表里的第一个函数的内存地址,而后强制转换成函数类型,便可调用来验证虚表机制。
泛型编程(Generic Programming,如下直接以GP称呼)是一种全新的程序设计思想,和OO,OB,PO这些为人所熟知的程序设计想法不一样的是GP抽象度更高,基于GP设计的组件之间耦合度低,没有继承关系,因此其组件间的互交性和扩展性都很是高。咱们都知道,任何算法都是做用在一种特定的数据结构上的,最简单的例子就是快速排序算法最根本的实现条件就是所排序的对象是存贮在数组里面,由于快速排序就是由于要用到数组的随机存储特性,便可以在单位时间内交换远距离的对象,而不仅是相临的两个对象,而若是用链表去存储对象,因为在链表中取得对象的时间是线性的即O[n],这样将使快速排序失去其快速的特色。也就是说,咱们在设计一种算法的时候,咱们老是先要考虑其应用的数据结构,好比数组查找,联表查找,树查找,图查找其核心都是查找,但由于做用的数据结构不一样将有多种不一样的表现形式。数据结构和算法之间这样密切的关系一直是咱们之前的认识。泛型设计的根本思想就是想把算法和其做用的数据结构分离,也就是说,咱们设计算法的时候并不去考虑咱们设计的算法将做用于何种数据结构之上。泛型设计的理想状态是一个查找算法将能够做用于数组,联表,树,图等各类数据结构之上,变成一个通用的,泛型的算法。
Container 经过Allocator得到数据存储空间;Algorithm 经过Iterator存取Container中的内容;Functor能够协助Algoritm完成不一样策略;Adapter能够修饰Container、Algorithm、Iterator。
参考连接: