STL介绍

(英文原文:http://www.sgi.com/tech/stl/stl_introduction.html)

STL(Standard Template Library)是一个容器类、算法和迭代器* container classes, algorithms和iterators*)的C++库;其提供了许多计算机科学的基础算法和数据结构。STL是一个通用generic)库,就是说其组件被大量地参数化:几乎每个STL中的组件都是一个模板template)。在使用STL之前,你应该确信自己理解模板在C++中如何工作。

容器(Containers)和算法(Algorithms)

如同许多类库,STL包括容器类: 其目的是能包含其他的对象。STL包括类vector, list, deque, set, multiset, map, multimap, hash_set, hash_multiset, hash_map和hash_multimap。每一个这样的类都是一个模板,并且能实例化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也包括一个很大的算法algorithm)集合,来操作存储在容器中的数据。例如,你能逆转一个vector中元素的顺序,使用reverse算法

reverse(v.begin(), v.end()); // v[0] == 17, v[1] == 10, v[2] == 7

关于调用reverse,这里有两点需要注意。第一,这是一个全局函数global function),而不是一个成员函数member function)。第二,其需要两个参数,而不是一个:其运作在一个范围range)内的元素上,而不是一个容器上。

这两个事实的原因是相同的:reverse,像其他STL算法一样,与STL容器类解耦。这意味着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];

这个例子使用了一个范围,就像逆转vector的例子一样:第一个用于逆转的参数是这个范围开始的指针,而第二个参数指向超越这个范围结束的元素。这个范围表示为 [A,A+6) ; 这里非对称符号提醒了这两个端点不同,第一个就是范围的开始,而第二个是超过一个one past)这个范围的结束。

迭代器(Iterators)

在逆转C array例子中,用于逆转的参数为类型double *。如果你逆转一个vector或一个list,那用于逆转的参数是什么?那就是,reverse申明其参数是什么,也就是v.begin()和v.end()返回什么?

答案是,用于reverse的参数为迭代器,这是指针的泛化。指针自己就是迭代器,这就是为什么逆转C array的元素是可能的。类似,vector申明嵌套类型iterator和const_iterator。在上面的例子中,由v.begin()和v.end()返回的类型为vector< int >::iterator。也有一些iterators,例如istream_iterator和ostream_iterator,根本不和容器相关联。

迭代器这种机制使得算法容器解耦成为可能:算法成为模板,并由迭代器的类型参数化,因此它们并不限制于单个容器类型。例如,考虑如何写一个算法来完成一个范围的线性搜索。这就是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需要三个参数:两个迭代器定义一个范围,而一个值用于在这个范围内搜索。其检查范围 [first,last) 内的每个迭代器,从开始到结束;并且,要么在其找到指向value的迭代器时,要么在其到达范围的结束时,停止。

first和last被申明为类型InputIterator,而InputIterator是一个模板参数。这就是,并没有实际的任何类型称作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;
}

概念(Concepts)和建模(Modeling)

有关任何模板函数所问的一个非常重要的问题,不仅仅是有关STL算法,是用于替换形式模板参数的正确替换的类型集是什么?例如,int * 或double * 可用于替换find的形式模板参数InputIterator。同样清楚的是,int 或double 就可能不行:find 使用表达式 first,而这里的引用操作符*(dereference operator)对类型 int 或类型 double 的对象没有意义。那么,基本的答案是find隐性地定义了一个类型上的需求集,而这就可被任何满足这些需求的类型所实例化。任何用于替换InputIterator的类型必须提供某些操作:其必须有可能比较该类型的两个对象是否相等,其必须有可能递增该类型的对象,其必须有可能引用该类型的对象来获得其指向的这个对象,等等。

find并不是唯一的有这样的需求集的STL 算法;用于 for_each 和 count,以及其它算法的参数,必须满足相同的需求。这些需求足够地重要,因此我们给它们一个名字:我们称这样的类型需求集为概念concpet),而我们称这个特定的概念为Input_Iterator。如果一个类型满足所有这些需求,我们就说它符合概念conforms to a concept),或这是概念的模型a model of a concept)。我们说 int* 是Input Iterator的一个模型,因为 int* 提供了Input Iterator需求所规定的所有操作。

概念并不是C++语言的一部分;没有办法在程序中申明概念,或申明一个特定类型为概念的一个模型。然而,概念是STL极为重要的部分。使用概念就使得有可能写出清晰区分接口interface)和实现implementation)的程序:find的作者仅需要考虑由Input Iterator所规定的接口,而不是符合那个概念的每个可能类型的实现。类似的,如果你想使用find,你仅仅需要确保你传递的参数为Input Iterator的模型。这也就是find和reverse能用于listS, vectorS, C arrays和许多其他类型的原因:根据概念来编程,而不是根据特定的类型,就使得重用软件组件和合成组件成为可能。

强化(Refinement:概念之间的层级关系)

实际上,Input Iterator是一个较弱的概念:也就是,其施加了非常少的需求。Input Iterator必须支持指针算术的一个子集(其必须能够用prefix和postfix operator++来递增一个Input Iterator),但不需要支持所有的指针算术操作。这对find足够,但一些其它的算法需要它们的参数满足更多需求。例如,reverse必须能够递减其参数,也能够递增;其会使用表达式 –last。根据概念,我们说reverse的参数必须是Bidirectional Iterator的模型,而非Input Interator。

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)。概念的强化非常像C++类的继承inheritance);我们使用一个不同单词的主要原因是强调强化应用于概念,而并非实际类型。

除了我们已经讨论的两个,实际有更多的三个迭代器概念:这五个迭代器概念是 Output Iterator, Input Iterator, Forward Iterator, Bidirectional Iterator, and Random Access Iterator;Forward Iterator是Input Iterator的强化,Bidirectional Iterator是Forward Iterator的强化,而Random Access Iterator是Bidirectional Iterator的强化。(Output Iterator与其它四个concepts都相关,但其不是这个强化层级的一部分:它不是任何其它迭代器概念的强化,并且没有其他迭代器概念是它的强化。)

容器类,像迭代器,组织成为概念层级。所有容器都是概念Container的模型;更加强化的概念,例如SequenceAssociative Container,描述了特定类型的容器。

STL的其它部分

如果你理解了算法、迭代器和容器,那么你就理解了应该了解STL的几乎一切。然而,STL还包括了几个其它类型的组件。

首先,STL包括几个实用程序utilities):在库的许多不同部分都使用的,非常基础的概念和函数。例如,概念Assignable描述了有赋值操作符assignment operators)和复制构造函数copy constructors)的类型;几乎所有STL类都是Assignable的模型,而几乎所有STL 算法需要它们的参数为Assignable的模型。

其次,STL包括一些用于分配和取消分配内存的低级机制。Allocators非常特殊,而为了几乎所有目的,你都可以安全地忽略它们。

最后,STL包括一个很大的函数对象function objects)的集合,也就是函子functors)。就像迭代器为指针的泛化,函数对象是函数的泛化:函数对象是你能够使用普通函数调用语法来调用的任何东西。有几个不同的概念与函数对象相关,包括Unary Function(需要单个参数的函数对象,即调用形式为 f(x) )和Binary Function(需要两个参数的函数对象,即调用形式为 f(x,y) )。函数对象是通用编程generic programming)的一个重要部分,因为它们允许不仅仅是对象类型上的抽象,也可以是所完成操作的抽象。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值