标准模板库,又称STL,是一个容器(container)类、 算法(algorithms)、迭代器(iterators)的C++库;它 提供了许多科学计算的基本算法和数据结构。STL 是一个泛型(generic)库,意味着他的组件大量地参 数化:在STL中几乎每个组件都是模板(template)。 在用STL之前,你必须理解C++中如何使用模板。 | <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> |
容器(Containers)和算法(algorithms)
和许多类库一样,STL包含容器(container)类:可以容纳其他对象的类。STL包含 vector, list, deque, set, multiset, map, multimap, hash_set, hash_multiset, hash_map 和 hash_multimap 类。这些类每个都是模板(template),能被实例化来包含各种类型的对象。例如,你可以用 vector ,就象常规的C语言中的数组,额外地,vector 能消除手工管理动态内存分配的杂事。
vector v(3); // 定义一个有三个元素的vector类 v[0] = 7; v[1] = v[0] + 3; v[2] = v[0] + v[1]; // v[0] == 7, v[1] == 10, v[2] == 17
STL也包含了大量的算法(algorithm),用来巧妙地处理存储于容器中的数据。例如,你能用 reverse 算法颠倒 vector 中元素的顺序。
reverse(v.begin(), v.end()); // v[0] == 17, v[1] == 10, v[2] == 7
调用 reverse,要注意两点。第一、它是个全局函数,而不是成员函数。第二、它需要两个参数而不是一个:它操作一定区间内的元素,而不是操作容器。在这个特例中,区间范围恰巧是整个容器 v。
这两点的实事的动机是一样的: reverse,其他STL算法也类似,弱化和STL容器类的联系。这意味着 reverse 不仅能用于颠倒 vector 中的元素次序,也能颠倒list甚至C数组中元素的次序。下面的程序是合法的。
double A[6] = { 1.2, 1.3, 1.4, 1.5, 1.6, 1.7 }; reverse(A, A + 6); for (int i = 0; i < 6; ++i) cout << "A[" << i << "] = " << A[i];
正如倒序 vector 的例子一样,这个例子也用一个区间:用于倒序的第一个参数是指向区间开始位置的指针,第二个参数指向越过区间末尾元素的位置。这个区间表示为 [A, A + 6); 不对称符号预示两个端点含义不同,第一个是区间的开始,第二个是区间末尾的下一个位置。
迭代器(Iterators)
在倒序C数组的例子中,参数类型无疑是 double* 类型。假如倒序 vector 或 list,倒序参数类型是什么?换句话说,reverse 声明它的参数到底是什么,v.begin() 和 v.end() 返回的到底是什么?
答案是 reverse 的参数是迭代器(iterator),是一个泛化的指针。指针自己也是 iterator,这就是为什么可以倒序C数组的元素。同样,vector声明了嵌套的类型 iterator 和 const_iterator。在上例中,v.begin() 和 v.end() 返回的类型是 vector ::iterator。也有一些迭代器,诸如 istream_iterator 和 ostream_iterator, 根本不能和任何容器关联。
iterator 是一种弱化容器和算法之间的关联的机制:算法是模板,并且被 iterator 的类型参数化,因此不受限于单一的容器类型。例如,考虑如何写个算法完成一定区间的线性查找。下面是STL的 find 算法:
template InputIterator find(InputIterator first, InputIterator last, const T& value) { while (first != last && *first != value) ++first; return first; }
find 有三个参数:两个 iterators 定义一个区间,第三个是从所定区间要查找的值。它检查在区间[first, last)内的每个 iterator,从头到尾处理,停止于当其发现一个 iterator 所指向的值等于 value,或当其到达区间的结尾。
first 和 last 定义为类型 InputIterator,InputIterator 是一个模板型参数。也就是说,实际上被调用的 InputIterator 不是真实的类型:当你调用 find,编译器用实参类型来取代形参 InputIterator 和 T。 假如 find 的前两个参数类型是 int*,第三个参数类型是 int,那么实际上你调用的是下面这个函数:
int* find(int* first, int* last, const int& value) { while (first != last && *first != value) ++first; return first; }
约束(Concepts)和类属实参(Modeling)
对于模板函数,不仅仅是STL算法,一个非常重要的问题是,用什么样的参数类型能够匹配形式模板参数?很明确,例如,int* 和 double* 可以取代 find 的形式模板参数。而 int 和 double就不行:find用表达式 *first,反引用操作符使得类型为 int 或 double 的对象没有意义。基本答案是,find 隐含地定义了对类型的要求,使其可以被满足这些要求的任何类型实例化。凡是取代InputIterator 的类型必须支持一定的操作:两个对象必须能比较大小,对象必须能够进行递增操作,对象反引用后必须能够获得其指向的对象,等等。
find 不是唯一的有如此要求的STL算法,对于 for_each ,count 和其他算法的参数,也必须满足同样的要求。这些要求非常重要,我们给它们一个名:我们称呼这样一组类型需要一个约束(concept),我们称这个特定的约束为 Input Iterator. 假如一个类型满足所有要求,我们说它遵守约束,或它是约束的类属实参(model)。我们说 int* 是 Input Iterator 的类属实参,因为 int* 提供 Input Iterator 所要求的所有操作。
约束不是C++语言的一部分;没有途径在程序中定义一个约束,或定义一个特定的类型是约束的类属实参。然而,约束是STL极其重要的一部分。用约束实现了写出接口分离的程序成为可能: find 的作者仅仅考虑约束 Input Iterator 指定的接口的实现,而不是实现和那个约束一致的任何可能的类型。同样地,假如你想用 find,你仅仅需要保证你传递的参数是 Input Iterator 的类属实参就可。这是为什么 find 和 reverse 能用于 lists, vectors, C arrays, 和许多其他类型的原因:按照约束编程,而不是按照特定的类型,使得重用软件组件和组件组合成为可能。
Refinement
Input Iterator 实际上是想当弱的约束(concept):就是说,它只有少数几个要求。一个 Input Iterator 必须支持指针运算的子集(它必须能用前缀或后缀 operator++ 来递增 Input Iterator ), 但无需支持指针的所有算法。这对于 find 是足够的,但一些其他的算法需要其参数满足其他的条件。例如, Reverse 参数必须能够像递增一样可以递减;因为它使用用表达 --last。 用约束的术语来讲,我们说 reverse 的参数必须是 Bidirectional Iterator ,而不是 Input Iterator。
Bidirectional Iterator 约束和 Input Iterator 约束很相近:他只是简单地增加了一点另外的条件。Bidirectional Iterator 类属实参是 Input Iterator 类属实参的子集:每一个 Bidirectional Iterator 的类属实参的数据类型,那它也是 Input Iterator 类属实参。例如,int* 既是 Bidirectional Iterator 的类属实参,也是 Input Iterator 的类属实参,但 istream_iterator 仅仅是 Input Iterator 的类属实参:它不符合更严格的 Bidirectional Iterator 的要求。
我们形容 Input Iterator 和 Bidirectional Iterator 的关系,通常说 Bidirectional Iterator 是 Input Iterator 的refinement 。约束的 Refinement 非常像 C++ 类的继承;我们用一个不同的词,而不叫“继承(inheritance)”的主要原因是强调 refinement 适用于约束而不是实际类型。
实际上还有三种迭代器约束,加上我们已经讨论的两种:五种迭代器约束是 Output Iterator, Input Iterator, Forward Iterator, Bidirectional Iterator, 和 Random Access Iterator; Forward Iterator 是 Input Iterator的 refinement,Bidirectional Iterator 是 Forward Iterator 的 refinement,Random Access Iterator 是 Bidirectional Iterator的 refinement。 (Output Iterator 和其他四种约束有关,但是它不是 refinement 层次上的关系:它不是任何其他迭代器约束的refinement ,其它迭代器约束也不是它的refinement 。) Iterator 浏览 有更全面的迭代器的信息。
像迭代器一样,容器类Container classes,被组织到约束体系中. 所有容器是 Container的模型; 更精确的约束,诸如 Sequence 和 Associative Container 描述特定类型的容器。
STL的其他部分
假如你理解了算法、迭代器和容器,你就理解了有关STL的绝大部分事情。可是,STL还包括其他几个组件。
首先,STL包含几个工具(utilities): :非常基本的约束和函数,被用于库的许多不同部分。例如,Assignable 约束, 描述用于赋值操作符和拷贝构造的类型;几乎所有的STL类是 Assignable 的类属实参,几乎所有的STL算法要求其参数是 Assignable 的类属实参。
第二,STL包含一些底层机制用来分配和释放内存。分配器( Allocators )是专门用于这种目的,在大多数情况你的确可以忽略它。
最后,STL包含了大量的函数对象( function objects),也称为仿函数( functors)。正如迭代器泛化指针,函数对象用来泛化函数:一个函数对象是能像函数语法一样调用的任何东西。 这里对函数对象有几个不同的约束,包括 Unary Function (函数对象接受一个参数,例如 f(x)) 和 Binary Function (函数对象接受两个参数,例如 f(x,y))。 函数对象是泛型编程的重要部分,他如同对象类型的抽象一样作用于操作。
原著:《Standard Template Library Programmer's Guide》