从大学时就开始学习C++,到现在近5年的时间了却很少用到STL。现在想想真得是对不起这门语言,也对不起宝贵的五年光阴。我钟爱C++,所以一定要完全搞懂它,理解它。爱一个人的前提是要懂他(她),爱一门语言也是这样。郑重地向C++说声“对不起!”。我会把不懂你的方面慢慢弥补,做到真正懂你。为了更好地学习STL,我采用边学习,边总结,边写博客的方法,希望能够形成一个学习专栏。这样既可以便于自己随时翻阅,又可以分享给有需要的人。当然在博客中,我有可能会引用到其它大牛博友的文章。为了尊重原创,我会给出参考博文的链接地址。另外,如果大家在文章中发现错误,希望在评论下方给出提示建议。

Now,开始学习C++中重要的标准库STL。

STL简介

STL的原名是“Standard Template Library”,翻译过来就是标准模板库。STL是C++标准库的一个重要组成部分,主要由六大组件构成。这六大组件是:

容器(Container)、算法(algorithm)、迭代器(iterator)、仿函数(functor)、适配器(adapter)、配置器(allocator)

1、容器(container)

容器可以分为三类即序列容器、关联容器和容器适配器。各类具体包含如下所示:

序列容器:vector、list、deque

关联容器:set、map、multiset、multimap

适配器容器:stack、queue、priority_queue

容器  特性 所在头文件
向量vector 在常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具有常数时间复杂度。对任意项的插入和删除的时间复杂度与到末尾的距离成正比,尤其对向量头的添加和删除代价比较高。 <vector>
双端队列deque 基本上与向量相同,不同点是,双端队列的两端插入和删除操作也是常量的时间复杂度。 <deque>
表list 对任意元素的访问与两端的距离成正比,但对某个位置上插入和删除时间复杂度为常数。 <list>
队列queue 插入只可以在尾部进行,删除、检索和修改只允许从头部进行。遵循FIFO的原则。 <queue>
栈stack LIFO:先进后出原则。只允许在序列头部进行插入、删除、检索和修改操作,时间复杂度为常数。 <stack>
集合set 内部实现机制是红黑树,每个结点都包含一个元素,结点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,具有快速查找的功能。 <set>
多重集合multiset 和集合基本相同,但可以支持重复元素。 <set>
映射map 由(键,值)对组成的集合,以某种作用于键对上的谓词排序。具有快速查找特性。 <map>
多重映射multimap 支持一键对应多个值的特性,具有快速查找功能。 <map>

2、算法(Algorithm)

算法部分主要在头文件<algorithm>,<numeric>,<functional>中。<algoritm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范 围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象。

3、迭代器(Adapter)

迭代器是用类模板(class template)实现的.重载了* ,-> ,++ ,-- 等运算符。

迭代器分5种:输入迭代器、输出迭代器、 前面迭代器、双向迭代器、 随机访问迭代器。

输入迭代器:向前读(只允许读);

输出迭代器:向前写(只允许写);

前向迭代器:向前读写;

双向迭代器:向前后读写;

随机迭代器:随机读写;

4、仿函数(Functor)

仿函数用类模板实现,重载了符号"()"。仿函数,又或叫做函数对象,是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;
};

5、适配器(Adapter)

适配器是用来修改其他组件接口的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就是个函数适配器。

6、空间配置器(Allocator)

STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提升。

SGI-STL的空间配置器有2种,一种仅仅对c语言的malloc和free进行了简单的封装,而另一个设计到小块内存的管理等,运用了内存池技术等。在SGI-STL中默认的空间配置器是第二级的配置器。

SGI使用时std::alloc作为默认的配置器。

  • alloc把内存配置和对象构造的操作分开,分别由alloc::allocate()::construct()负责,同样内存释放和对象析够操作也被分开分别由alloc::deallocate()和::destroy()负责。这样可以保证高效,因为对于内存分配释放和构造析够可以根据具体类型(type traits)进行优化。比如一些类型可以直接使用高效的memset来初始化或者忽略一些析构函数。对于内存分配alloc也提供了2级分配器来应对不同情况的内存分配。
  • 第一级配置器直接使用malloc()和free()来分配和释放内存。第二级视情况采用不同的策略:当需求内存超过128bytes的时候,视为足够大,便调用第一级配置器;当需求内存小于等于128bytes的时候便采用比较复杂的memeory pool的方式管理内存。
  • 无论allocal被定义为第一级配置器还是第二级,SGI还为它包装一个接口,使得配置的接口能够符合标准即把配置单位从bytes转到了元素的大小:
template<class T, class Alloc>
class simple_alloc
{
public:
static T* allocate(size_t n)
{
return == n ? : (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 ( != 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。

参考链接:

STL(Standard Template Library)简介

STL学习小结

学习STL-介绍一下STL的更多相关文章

  1. STL学习系列之一——标准模板库STL介绍

    库是一系列程序组件的集合,他们可以在不同的程序中重复使用.C++语言按照传统的习惯,提供了由各种各样的函数组成的库,用于完成诸如输入/输出.数学计算等功能. 1. STL介绍 标准模板库STL是当今每 ...

  2. 跟我学STL系列(1)——STL入门介绍

    一.引言 最近这段时间一直都在自学C++,所以这里总结下自己这段时间的学习过程,通过这种方式来巩固自己学到的内容和以备后面复习所用,另外,希望这系列文章可以帮助到其他自学C++的朋友们. 由于本人之前 ...

  3. C++ 标准模板库介绍(STL)

    1. STL 基本介绍 C++ STL(标准模板库)是惠普实验室开发的一系列软件的统称,是一套功能强大的 C++ 模板类.STL的目的是为了标准化组件,这样就不用重新开发,让后来者可以使用现成的组件, ...

  4. STL学习系列一:STL(标准模板库)理论基础

    STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间. STL的从广 ...

  5. C++ STL介绍——简介

    目录 1.什么是STL 2.STL中六大组件 2.1 容器(Container) 2.2 迭代器(Iterator) 2.3 算法(Algorithm) 2.4 仿函数(Functor) 2.5 适配 ...

  6. STL笔记(2) STL之父访谈录

    年3月,dr.dobb's journal特约记者, 著名技术书籍作家al stevens采访了stl创始人alexander stepanov. 这份访谈纪录是迄今为止对于stl发展历史的最完备介绍 ...

  7. STL源代码分析——STL算法remove删除算法

    前言 因为在前文的<STL算法剖析>中,源代码剖析许多.不方便学习,也不方便以后复习,这里把这些算法进行归类.对他们单独的源代码剖析进行解说.本文介绍的STL算法中的remove删除算法. ...

  8. STL源代码分析——STL算法merge合并算法

    前言 因为在前文的<STL算法剖析>中.源代码剖析许多.不方便学习.也不方便以后复习,这里把这些算法进行归类.对他们单独的源代码剖析进行解说.本文介绍的STL算法中的merge合并算法. ...

  9. STL源代码分析——STL算法sort排序算法

    前言 因为在前文的<STL算法剖析>中,源代码剖析许多,不方便学习,也不方便以后复习.这里把这些算法进行归类,对他们单独的源代码剖析进行解说.本文介绍的STL算法中的sort排序算法,SG ...

  10. C++标准模板库Stand Template Library(STL)简介与STL string类

    参考<21天学通C++>第15和16章节,在对宏和模板学习之后,开启对C++实现的标准模板类STL进行简介,同时介绍简单的string类.虽然前面对于vector.deque.list等进 ...

随机推荐

  1. PLSQL_基础系列09_时间戳记TIMESTAMP(案例)

    2013-11-09 Created By BaoXinjian

  2. PLSQL_性能优化系列14_Oracle High Water Level高水位分析

    2014-10-04 Created By BaoXinjian 一.摘要 PLSQL_性能优化系列14_Oracle High Water Level高水位分析 高水位线好比水库中储水的水位线,用于 ...

  3. python(12)给文件读写上锁

    目的:当我们用脚本去爬取数据或者向文件中写数据的时候,有时候需要两个或者多个脚本同时向一个文件中读写 于是乎就会出现写乱的情况,于是乎我们就需要把正在写的文件先锁起来,只让当前的写,写完后再释放 代码 ...

  4. Python2.7.5 安装(转载)

    From:http://www.cnblogs.com/balaamwe/p/3480430.html From:http://www.chgon.com/?p=1340 安装python2.7.5纠 ...

  5. java中使用正则表达式

    1.用正则表达式分割字符串 Pattern SPLIT2 = Pattern.compile("[,]"); String[] tmpStrings1 = SPLIT2.split ...

  6. JAVA CAS原理深度分析

    参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/reso ...

  7. Java多线程之捕获异常

    1.主线程不能捕获到子线程的异常 package Thread.Exection; import java.util.concurrent.ExecutorService; import java.u ...

  8. Getting started with SciPy for .NET

    Getting started with SciPy for .NET 1.) IronPython Download and install IronPython 2.7, this will re ...

  9. ramBufferSizeMB

    索引算法确定 的情况下,影响Lucene索引速度的因素 MaxBufferedDocs这个参数默认是disabled的,因为Lucene中还用另外一个参数(RAMBufferSizeMB)控制这个bu ...

  10. Sqlserver中存储过程,触发器,自定义函数(一)

    Sqlserver中存储过程,触发器,自定义函数 1.存储过程有关内容存储过程的定义:存储过程的分类:存储过程的创建,修改,执行:存储过程中参数的传递,返回与接收:存储过程的返回值:存储过程使用游标. ...