STL 六大组件概要

数据结构与算法

相信大家都听过一句话:程序 = 数据结构 + 算法
我们在写程序时,实际上就是在对给定的数据进行一系列的操作,然后将处理的结果返回给需要接收的地方,这里的地方可以指程序本身,也可以指返回给用户,你懂我意思的。

数据结构就是各种具有相似性质或属性的数据的一种组织关系(逻辑的关系和物理的存储方式),无非就那几种,集合线性结构树状结构图状结构或网状结构。而我们现实生活中遇到的绝大部分问题,都可以将我们需要处理的数据抽象组织成上述几种结构。

算法就是处理数据的各种操作步骤的集合,常见的操作有对具有某种结构的数据集中的数据进行排序,或者 查找 数据集中某个指定的数据等。

这些经典的数据结构和算法都是相当成熟的,而我们实际程序开发中又经常会用到相关的数据结构或算法,如果我们每个人都去实现自己的数据结构或者算法,先不考虑我们编写的效率如何,单就实现这些功能来说就是一个浪费时间浪费人力的工作,这样软件开发就会出现在不同的地点不同的人在相同的时间做着重复而无效率保证的工作。

软件设计的目标以及金科玉律就是:复用

STL

对于使用 C++ 为语言工具做开发的人而言,幸运的是,有那么一帮大神帮我们实现了一个叫 STL(Standard Template Library) 的东西,也就是标准模板库,是泛型编程的一个很好的实例或实践。为什么要用模板?因为模板可以通用,你想处理什么类型的数据都可以,包括内置类型或者用户自定义类型等,这么一本万利的东西,你说要不要用?

STL 为我们实现了一系列的数据结构以及一些典型的算法,而且效率肯定是有保证的,毕竟是大神设计的,所以我们大可肆无忌惮的使用 STL 为我们提供的东西,而不是重复造轮子(关键是造出来的没别人的好)。

STL 就是一种高复用的软件产品。

容器(containers)

当然,STL 并不是简单的为我们实现各种数据结构提供给我们直接使用,而是将这些不同的数据结构设计为各种不同的容器,我们都知道容器是用来盛东西用的,比如碗用来盛饭、水杯用来盛水等,这里的碗和水杯就是两种不同的容器。而 STL 为我们提供的容器实际上就是用来存放我们需要处理的数据,具有不同结构的数据使用不同的容器,每一种容器就相当于是一种数据结构。

STL 中的容器是一种 class template,常见的容器有 vectorlistdequeset 以及 map 等。

算法(algorithms)

有了数据,接下来就是对这些数据进行各种处理了,STL 为我们提供了各种常用的算法,如 sortsearchcopyerase… 当然,某些容器也根据自身的特点实现了适应于本容器特性的算法或操作(比通用算法中更高效),成为其成员函数。因此,对于同一个算法,如果标准库和容器本身都提供了,那么我们优先使用容器本身提供的那个,这样在效率上更有保证。

STL 算法是一种 function template

现在有个问题,对于容器本身实现的那些算法,由于其是容器的成员函数,因此自然而然的可以访问并操作容器内部的数据,那对于那些通用的算法呢?如何访问到容器内部的数据?数据可是容器私有的,外界不能直接访问。

为了解决这个问题,就需要一个中间桥梁,使算法能够有办法访问到给定容器中的数据。而 STL 所设计的这个桥梁就是迭代器

迭代器(iterators)

迭代器所扮演的角色就是容器算法之间的胶合剂,本质上是一个所谓的 泛型指针 。从实现的角度来看,迭代器就是一种将 operator*operator->operator++ 以及 operator-- 等指针相关操作进行重载的 class template

所有的 STL 容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。

原生指针(native pointer)也是一种迭代器。

配置器(allocators)

容器要放数据就得需要存储空间,STL 容器中使用的内存空间分配不是直接使用我们熟知的 newdelete, 而是对其进行了包装,成为一个 class template,负责空间配置、管理和释放的工作。每个容器都有一个默认的配置器,当然也可以根据需要去更改默认的配置器,而使用 STL 提供的其他配置器。

空间分配器容器算法都有了,好像 STL 已经可以正常的工作了,拥有这3个组件的 STL 的确可以工作,但是你有没有想过,对于一个算法而言,就拿最简单的排序来说,sort 算法内部通过对容器中的数据进行两两比较大小,从而进行排序;对于 intfloat以及其它的一些内置类型,可以简单的直接使用 < 运算符来进行两个数据的大小判断,这没什么问题,但是对于用户自定义的类型呢?

比如我定义了一个苹果 class:class Apple { ... };,选择 vector 这个容器来存放我的苹果,现在我要对容器里的苹果进行排序,按重量(苹果内部有一个表示重量的数据成员)的大小从小到大进行排,那么对于 sort 算法来说,显然直接将容器丢给它还不够,因为容器里的数据不是内置类型,它不知道如何来比较两个苹果的大小,我们必须显示的告诉 sort 该如何比较我们的苹果大小。

那么现在问题来了,我们如何来告诉 sort 以及如何实现我们自己的比较大小(对于 sort 来说,比较大小是一个策略问题,我只提供默认的比较方法,至于其他的比较策略,用户可以自行定义)的方法?

STL 很贴心的为我们提供了仿函数这个东东。

仿函数(functors)

在 STL 中,仿函数其实就是一个 classclass template,只不过这些类重载了 operator() 这个运算符,使这个类所产生的对象的行为类似函数。仿函数的作用就是服务于算法,可以作为算法的某种策略(policy),比如对于 sort 来说比较大小的策略。

一般的函数指针也可以视为是狭义的仿函数。

对于 STL 提供的算法,除了内置默认的相关策略或准则,我们也可以通过函数仿函数来实现我们自己的算法策略,然后将其通过参数的方法传递给算法使用。

通过上述描述,我们知道:

  • 配置器服务于容器
  • 仿函数服务于算法
  • 迭代器和容器共同为算法服务

那么我们可能会猜想:STL 有没有提供什么东西是用来服务配置器容器迭代器以及仿函数的吗?

猜的没错,STL 还真提供了一个叫做 适配器或配接器 的东西来服务上述几个组件,配置器组件除外。

配接器或适配器(adapters)

配接器就是一种用来修饰容器迭代器仿函数接口的东西。

有点类似于以前老式的变压器,记得小时候家里买了彩电(彩色电视机),除了彩电,还会买一个变压器,将变压器插在房间的插座中,然后将彩电的插头插在变压器上,在这里,变压器就是将房间的电压与彩电需要的电压进行转换或者说进行适配,使房间提供的电压通过变压器能够满足彩电的需要,不管是因为房间的电压不稳定还是彩电需要的电压和房间提供的电压不一致,通过变压器,就能正常的观看彩电里的节目了。

配接器就是这样,通过小工程的改造 容器、迭代器或仿函数接口,使它们通过另一种形式(Adapters的形式)来服务于算法等。

改变 functor 接口者,称为 function adapter
改变 container 接口者,称为 container adapter
改变 iterator 接口者,称为 iterator adapter

通常 Adapters 的实现方式是在其内部含有(组合)一个 容器、迭代器或仿函数。

例如,STL 提供的 queuestack 就是一种 container adapter,而不是一个容器,因为它们底部完全借助 deque,所有的操作都由底层的 deque 供应。

最后放出一张 STL 六大组件的关系图:
STL六大组件的交互关系

好了,STL 的六大组件基本概要已经讲完了,以上内容基于查阅的资料以及自己的理解,有不对的地方还请各位看客指出,欢迎各位拍砖。

参考资料:

  • [1] 《STL 源码剖析》侯捷

猜你喜欢

转载自blog.csdn.net/pl20140910/article/details/81480281
今日推荐