STL就是Standard Template Library,标准模板库。这可能是一个历史上最令人兴奋的工具的最无聊的术语。从根本上说,STL是一些“容器”的集合,这些“容器”有list, vector,set,map等,STL也是算法和其它一些组件的集合。这里的“容器”和算法的集合指的是世界上很多聪明人很多年的杰作。是C++标准库的一个重要组成部分,它由Stepanov and Lee等人最先开发,它是与C++几乎同时开始开发的;一开始STL选择了Ada作为实现语言,但Ada有点不争气,最后他们选择了C++,C++中已经有了模板。STL又被添加进了C++库。1996年,惠普公司又免费公开了STL,为STL的推广做了很大的贡献。STL提供了类型安全、高效而易用特性的STL无疑是最值得C++程序员骄傲的部分。每一个C++程序员都应该好好学习STL。大体上包括Container(容器)、algorithm(算法)和iterator(迭代器),容器和算法通过迭代器可以进行无缝连接。
一、基础知识
1、泛型技术
泛型技术的实现方法有多种,比如模板,多态等。模板是编译时决定,多态是运行时决定,其他的比如RTTI也是运行时确定。多态是依靠虚表在运行时查表实现的。比如一个类拥有虚方法,那么这个类的实例的内存起始地址就是虚表地址,可以把内存起始地址强制转换成int*,取得虚表,然后(int*)*(int*)取得虚表里的第一个函数的内存地址,然后强制转换成函数类型,即可调用来验证虚表机制。
泛型编程(generic programming,以下直接以GP称呼)是一种全新的程序设计思想,和OO,OB,PO这些为人所熟知的程序设计想法不同的是GP抽象度更高,基于GP设计的组件之间偶合度底,没有继承关系,所以其组件间的互交性和扩展性都非常高。我们都知道,任何算法都是作用在一种特定的数据结构上的,最简单的例子就是快速排序算法最根本的实现条件就是所排序的对象是存贮在数组里面,因为快速排序就是因为要用到数组的随机存储特性,即可以在单位时间内交换远距离的对象,而不只是相临的两个对象,而如果用联表去存储对象,由于在联表中取得对象的时间是线性的即O[n],这样将使快速排序失去其快速的特点。也就是说,我们在设计一种算法的时候,我们总是先要考虑其应用的数据结构,比如数组查找,联表查找,树查找,图查找其核心都是查找,但因为作用的数据结构不同将有多种不同的表现形式。数据结构和算法之间这样密切的关系一直是我们以前的认识。泛型设计的根本思想就是想把算法和其作用的数据结构分离,也就是说,我们设计算法的时候并不去考虑我们设计的算法将作用于何种数据结构之上。泛型设计的理想状态是一个查找算法将可以作用于数组,联表,树,图等各种数据结构之上,变成一个通用的,泛型的算法。
2、四种类型转换操作符
static_cast 将一个值以符合逻辑的方式转换。应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类。
例如:float x;
Count<<static_cast<int>(x);//把x作为整型值输出
dynamic_cast 将多态类型向下转换为其实际静态类型。只用于对象的指针和引用。当用于多态类型时,它允许任意的隐式类型转换以及相反过程。dynamic_cast会检查操作是否有效。也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.
例如:class Car;
class Cabriolet:public Car{
…
};
class Limousline:public Car{
…
};
void f(Car *cp)
{
Cabriolet *p = dynamic_cast< Cabriolet > cp;
}
reinterpret_cast 转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
例如:
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B *>(a);
const_cast一般用于强制消除对象的常量性。
例如:
class C {};
const C *a = new C;
C *b = const_cast<C *>(a);
其它三种操作符是不能修改一个对象的常量性的。
3、explicit修饰的构造函数不能担任转换函数。在很多情况下,隐式转换是有意的,并且是正当的。但有时我们不希望进行这种自动的转换。
例如:为了避免这样的隐式转换,应该象下面这样显式声明该带单一参数的构造函数:
class String {
int size;
char *p;
//..
public:
// 不要隐式转换
explicit String (int sz);
String (const char *s, int size n = 0); // 隐式转换
};
void f ()
{
String s(10);
s = 100; // 现在编译时出错;需要显式转换:
s = String(100); // 好;显式转换
s = "st"; // 好;此时允许隐式转换
}
4、命名空间namespace
解决在使用不同模块和程序库时,出现名称冲突问题。
5、C++标准程序库中的通用工具。由类和函数构成。这些工具包括:
数种通用类型
一些重要的C函数
数值极值
二、STL六大组件
容器(Container)
算法(Algorithm)
迭代器(Iterator)
仿函数(Function object)
适配器(Adaptor)
空间配置器(allocator)
1、容器
作为STL的最主要组成部分--容器,分为向量(vector),双端队列(deque),表(list),队列(queue),堆栈(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。
容器 |
特性 |
所在头文件 |
向量vector |
可以用常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具有常数时间复杂度,对任意项的插入和删除就有的时间复杂度与到末尾的距离成正比,尤其对向量头的添加和删除的代价是惊人的高的 |
<vector> |
双端队列deque |
基本上与向量相同,唯一的不同是,其在序列头部插入和删除操作也具有常量时间复杂度 |
<deque> |
表list |
对任意元素的访问与对两端的距离成正比,但对某个位置上插入和删除一个项的花费为常数时间。 |
<list> |
队列queue |
插入只可以在尾部进行,删除、检索和修改只允许从头部进行。按照先进先出的原则。 |
<queue> |
堆栈stack |
堆栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项。即按照后进先出的原则 |
<stack> |
集合set |
由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,具有快速查找的功能。但是它是以牺牲插入删除操作的效率为代价的 |
<set> |
多重集合multiset |
和集合基本相同,但可以支持重复元素具有快速查找能力 |
<set> |
映射map |
由{键,值}对组成的集合,以某种作用于键对上的谓词排列。具有快速查找能力 |
<map> |
多重集合multimap |
比起映射,一个键可以对应多了值。具有快速查找能力 |
<map> |
STL容器能力表:
2、算法
算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。< algorithm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范 围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象。
STL的算法也是非常优秀的,它们大部分都是类属的,基本上都用到了C++的模板来实现,这样,很多相似的函数就不用自己写了,只要用函数模板就可以了。
我们使用算法的时候,要针对不同的容器,比如:对集合的查找,最好不要用通用函数find(),它对集合使用的时候,性能非常的差,最好用集合自带的find()函数,它针对了集合进行了优化,性能非常的高。
3、迭代器
它的具体实现在<itertator>中,我们完全可以不管迭代器类是怎么实现的,大多数的时候,把它理解为指针是没有问题的(指针是迭代器的一个特例,它也属于迭代器),但是,决不能完全这么做。
迭代器功能 |
||
输入迭代器 Input iterator |
向前读 Reads forward |
istream |
输出迭代器 Output iterator |
向前写 Writes forward |
ostream,inserter |
前向迭代器 Forward iterator |
向前读写 Read and Writes forward |
|
双向迭代器 Bidirectional iterator |
向前向后读写 Read and Writes forward and backward |
list,set,multiset,map,mul timap |
随机迭代器 Random access iterator |
随机读写 Read and Write with random access |
vector,deque,array,string |
4、仿函数
仿函数,又或叫做函数对象,是STL六大组件之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本。例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素相等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。仿函数(functor)又称之为函数对象(function object),其实就是重载了()操作符的struct,没有什么特别的地方。
如以下代码定义了一个二元判断式functor:
struct IntLess
{
bool operator()(int left, int right) const
{
return (left < right);
};
};
为什么要使用仿函数呢?
1).仿函数比一般的函数灵活。
2).仿函数有类型识别,可以作为模板参数。
3).执行速度上仿函数比函数和指针要更快的。