(source:http://www.sgi.com/tech/stl/stl_introduction.html)
STL是一个container classes, algorithms和iterators的C++库;其提供许多计算机科学的基础算法和数据结构。STL是一个generic库,就是说其组件被大量地参数化:几乎每个STL中的组建都是一个template。在使用STL之前,你应该确信自己理解template在C++中如何工作。
如同许多class库,STL包括container类: 其目的是包含其他对象的类。STL包括类vector, list, deque, set, multiset, map, multimap, hash_set, hash_multiset, hash_map和hash_multimap。每一个这样的类都是一个template,并且能实例化(instantiated)包括任意类型的对象。你能,例如,像使用普通C array的方式来使用vector<int>,除了vector免除了手工管理内存分配的苦差事。
vector<int> v(3); // Declare a vector of 3 elements. v[0] = 7; v[1] = v[0] + 3; v[2] = v[0] + v[1]; // v[0] == 7, v[1] == 10, v[2] == 17
STL也包括一个很大的algorithms集合,来操作存储在containers中的数据。例如,你能逆转一个vector中元素的顺序,使用reverse算法
reverse(v.begin(), v.end()); // v[0] == 17, v[1] == 10, v[2] == 7
关于调用reverse,这里有两点需要注意。第一,这是一个全局函数,而不是一个成员函数。第二,其需要两个参数,而不是一个:其运作在一个range内的元素上,而不是一个container上。
这两个事实的原因是相同的:reverse,像其他STL algorithms一样,与STL container类解耦。者意味着reverse不仅能用于逆转vectors中的元素,也能逆转lists中的元素,甚至C arrays中的元素。以下程序也是有效的:
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];
这个例子使用了一个range,就像逆转vector的例子一样:第一个用于逆转的参数是这个range开始的指针,而第二个参数指向超越这个range结束的元素。这个range表示为 [A, A+6); 这里非对称符号提醒了这两个端点不同,第一个就是range的开始,而第二个是one past这个range的结束。
在逆转C array例子中,用于逆转的参数为类型double *。如果你逆转一个vector或一个list,那用于逆转的参数是什么?那就是,reverse申明其参数是什么,也就是v.begin()和v.end()返回什么?
答案是,用于reverse的参数为iterators,这是pointers的泛化。Pointers自己就是iterators,这就是为什么逆转C array的元素时可能的。类似,vector申明嵌套类型iterator和const_iterator。在上面的例子中,由v.begin()和v.end()返回的类型为vector<int>::iterator。也有一些iterators,例如istream_iterator和ostream_iterator,根本不和containers相关联。
Iterators这种机制使得algorithms从containers解耦成为可能:algorithms为templates,并由iterator的类型参数化,因此它们并不限制于单个container类型。例如,考虑如何写一个algorithm来完成一个范围的线性搜索。这就是STL的find算法:
template <class InputIterator, class T> InputIterator find(InputIterator first, InputIterator last, const T& value) { while (first != last && *first != value) ++first; return first; }
Find需要三个参数:两个iterators定义一个范围,而一个值用于在这个范围内搜索。其检查范围[first, last)内的每个iterator,从开始到结束,并且,要么在其找到指向value的iterator时,要么在其到达范围的结束时停止。
First和last被申明为类型InputIterator,而InputIterator时一个template parameter。这就是,并没有实际的任何类型称作InputIterator:当你调用find,编译器把形式类型参数(formal type parameters)InputIterator和T替换为参数的实际类型。如果find前两个参数为类型int*,第三个为类型int,那么这就像你调用如下的函数:
int* find(int* first, int* last, const int& value) { while (first != last && *first != value) ++first; return first; }
有关任何template function所问的一个非常重要的问题,不仅仅是有关STL algorithms,是用于替换形式模板参数的正确替换的类型集是什么?例如,int*或double*可用于替换find的形式模板参数InputIterator。同样清楚的是,int或double就可能不行:find使用表达式*first,而这里的引用操作符(dereference operator)对类型int或类型double的对象没有意义。那么,基本的答案是find隐性地定义了一个类型上的需求集,而这就可被任何满足这些需求的类型所实例化。任何用于替换InputIterator的类型必须提供某些操作:其必须有可能比较该类型的两个对象是否相等,其必须有可能递增该类型的对象,其必须有可能引用该类型的对象来获得其指向的这个对象,等等。
Find并不是唯一的有这样的需求集的STL algorithm;用于for_each和count,以及其它algorithms的参数,必须满足相同的需求。这些需求足够地重要,因此我们给它们一个名字:我们称这样的类型需求集为concpet,而我们称这个特定的concept为Input_Iterator。如果一个类型满足所有这些需求,我们就说它conforms to a concept(符合concept),或这是a model of a concept(concept的一个model)。我们说int*是Input Iterator的一个model,因为int*提供了Input Iterator需求所规定的所有操作。
Concepts并不是C++语言的一部分;没有办法在程序中申明concept,或申明一个特定类型为concept的一个model。然而,concepts是STL极为重要的部分。使用concepts就使得有可能写出清晰区分interface和implementation的程序:find的作者仅需要考虑由Input Iterator所规定的interface,而不是符合那个concept的每个可能类型的实现。类似的,如果你想使用find,你仅仅需要确保你传递的参数为models of Input Iterator。这也就是find和reverse能用于listS, vectorS, C arrays和许多其他类型的原因:根据concepts来编程,而不是根据特定的类型,就使得重用软件组件和合成组件成为可能。
实际上,Input Iterator是一个较弱的concept:也就是,其施加了非常少的需求。Input Iterator必须支持pointer算术的一个子集(其必须可能用prefix和postfix operator++来递增一个Input Iterator),但不需要支持所有的pointer算术操作。这对find足够,但一些其它的algorithms需要它们的参数满足更多需求。例如,Reverse必须能够递减其参数,也能够递增;其会使用表达式 --last。根据concepts,我们说reverse的参数必须是models of Bidirectional Iterator,而非Input Interator。
Bidirectional Iterator concept与Input Iterator非常类似:其简单地施加了一些附加的需求。作为models of Bidirectional Iterator的类型是models of Input Iterator的类型的一个子集:每个model of Bidirectional Iterator的type都是model of Input Iterator。例如,int*同时是model of Bidirectional Iterator和model of Input Iterator,但istream_iterator仅仅是model of Input Iterator:其并不符合更严格的Bidirectional Iterator需求。
我们描述Input Iterator和Bidirectional Iterator之间的关系为Bidirectional Iterator是Input Iterator的refinement。concept的refinement非常像C++类的inheritance;我们使用一个不同单词的主要原因是强调refinement应用于concepts,而并非实际类型
除了我们已经讨论的两个,实际有更多的三个iterator concepts:这五个iterator concepts是 Output Iterator, Input Iterator, Forward Iterator, Bidirectional Iterator, and Random Access Iterator;Forward Iterator是Input Iterator的一个refinement,Bidirectional Iterator是Forward Iterator的一个refinement,而Random Access Iterator是Bidirectional Iterator的一个refinement。(Output Iterator与其它四个concepts都相关,但其不是这个hierarchy of refinement的一部分:它不是任何其它iterator concepts的refinement,并且没有其他iterator concepts是它的refinement。)
Container类,像iterators,组织成为hierarchy of concepts。所有containers是models of the concept Container,更加refined concpets,例如Sequence和Associative Container,描述特定类型的containers。
如果你理解了algorithms, iterators, and containers,那么你就理解了应该了解STL的几乎一切。然而,STL包括了几个其它类型的组件。
首先,STL包括几个utilities:非常基础的concepts和functions,用于库的许多不同部分。例如,concept Assignable描述了有assignment operators和copy constructors的类型;几乎所有STL类都是models of Assignable,而几乎所有STL algorithms需要它们的参数为models of Assignmable。
其次,STL包括一些用于allocating和deallocating内存的低级机制。Allocators非常特殊,而为了几乎所有目的,你都可以安全地忽略它们。
最后,STL包括一个很大的function objects的集合,也就是functors。就像iterators为pointers的泛化,function objects是functions的泛化:function object是你能够使用普通函数调用语法来调用的任何东西。有几个不同的concepts与function objects相关,包括Unary Function(需要单个参数的function object,即调用形式为f(x))和Binary Function(需要两个参数的function object,即调用形式为f(x,y))。Function objects是generic programming的一个重要部分,因为它们允许不仅仅是对象类型上的抽象,也可以是所完成操作的抽象。