C++泛型编程

有了一些使用STL的经验后,来看一看底层理念。STL是一种泛型编程( generic programming)。面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法。它们之间的共同点是抽象和创建可重用代码,但它们的理念绝然不同。泛型编程旨在编写独立于数据类型的代码。在C++中,完成通用程序的工具是模板。当然,模板使得能够按泛型定义函数或类,而STL通过通用算法更进了一步。模板让这一切成为可能,但必须对元素进行仔细地设计。为解模板和设计是如何协同工作的,来看一看需要迭代器的原因。

理解迭代器是理解STL的关键所在。模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。因此,它们都是STL通用方法的重要组成部分。不同的算法对迭代器的要求也不同。例如,查找算法需要定义++运算符,以便迭代器能够遍历整个容器;它要求能够读取数据,但不要求能够写数据(它只是查看数据,而并不修改数据)。而排序算法要求能够随机访问,以便能够交换两个不相邻的元素。如果iter是一个迭代器,则可以通过定义+运算符来实现随机访问,这样就可以使用像iter + 10这样的表达式了。另外,排序算法要求能够读写数据。STL定义了5种迭代器,并根据所需的迭代器类型对算法进行了描述。这5种迭代器分别是输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器。

STL有若干个用C++语言无法表达的特性,如迭代器种类。因此,虽然可以设计具有正向迭代器特征的类,但不能让编译器将算法限制为只使用这个类。原因在于,正向迭代器是一系列要求,而不是类型。所设计的迭代器类可以满足这种要求,常规指针也能满足这种要求。STL算法可以使用任何满足其要求的迭代器实现。STL文献使用术语概念(concept)来描述一系列的要求。因此,存在输入迭代器概念、正向迭代器概念,等等。顺便说一句,如果所设计的容器类需要迭代器,可考虑STL,它包含用于标准种类的迭代器模板。概念可以具有类似继承的关系。例如,双向迭代器继承了正向迭代器的功能。然而,不能将C++继承机制用于迭代器。例如,可以将正向迭代器实现为一个类,而将双向迭代器实现为一个常规指针。因此,对C++而言,这种双向迭代器是一种内置类型,不能从类派生而来。然而,从概念上看,它确实能够继承。有些STL文献使用术语改进(refinement)来表示这种概念上的继承,因此,双向迭代器是对正向迭代器概念的一种改进。概念的具体实现被称为模型(model)。因此,指向int 的常规指针是一个随机访问迭代器模型,也是一个正向迭代器模型,因为它满足该概念的所有要求。

迭代器是广义指针,而指针满足所有的迭代器要求。迭代器是STL算法的接口,而指针是迭代器,因此 STL 算法可以使用指针来对基于指针的非 STL容器进行操作。

因此,使用容器能够大幅度提高算法开发效率:

1.vector

前面介绍了多个使用vector模板的例子,该模板是在 vector头文件中声明的。简单地说,vector是数组的一种类表示,它提供了自动内存管理功能,可以动态地改变vector对象的长度,并随着元素的添加和删除而增大和缩小。它提供了对元素的随机访问。在尾部添加和删除元素的时间是固定的,但在头部或中间插入和删除元素的复杂度为线性时间。除序列外,vector还是可反转容器(reversible container)概念的模型。这增加了两个类方法: rbegin( )和rend( ),前者返回一个指向反转序列的第一个元素的迭代器,后者返回反转序列的超尾迭代器。因此,如果dice是一个vector<int>容器,而Show(int)是显示一个整数的函数。

2.deque

deque模板类(在deque头文件中声明)表示双端队列(double-ended queue),通常被简称为deque。在STL 中,其实现类似于vector容器,支持随机访问。主要区别在于,从deque对象的开始位置插入和删除元素的时间是固定的,而不像vector中那样是线性时间的。所以,如果多数操作发生在序列的起始和结尾处,则应考虑使用deque数据结构。为实现在 deque两端执行插入和删除操作的时间为固定的这一目的,deque对象的设计比 vector对象更为复杂。因此,尽管二者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但vector容器执行这些操作时速度要快些。

3.list

list模板类(在list 头文件中声明)表示双向链表。除了第一个和最后一个元素外,每个元素都与前后的元素相链接,这意味着可以双向遍历链表。list 和vector之间关键的区别在于,list 在链表中任一位置进行插入和删除的时间都是固定的((vector模板提供了除结尾处外的线性时间的插入和删除,在结尾处,它提供了固定时间的插入和删除)。因此,vector强调的是通过随机访问进行快速访问,而list 强调的是元素的快速插入和删除。与vector 相似,list 也是可反转容器。与vector不同的是,list 不支持数组表示法和随机访问。与矢量迭代器不同,从容器中插入或删除元素之后,链表迭代器指向元素将不变。我们来解释一下这句话。例如,假设有一个指向vector容器第5个元素的迭代器,并在容器的起始处插入一个元素。此时,必须移动其他所有元素,以便腾出位置,因此插入后,第5个元素包含的值将是以前第4个元素的值。因此,迭代器指向的位置不变,但数据不同。然后,在链表中插入新元素并不会移动已有的元素,而只是修改链接信息。指向某个元素的迭代器仍然指向该元素,但它链接的元素可能与以前不同。

4.关联容器

关联容器(associative container)是对容器概念的另一个改进。关联容器将值与键关联在一起,并使用键来查找值。例如,值可以是表示雇员信息(如姓名、地址、办公室号码、家庭电话和工作电话、健康计划等)的结构,而键可以是唯一的员工编号。为获取雇员信息,程序将使用键查找雇员结构。前面说过,对于容器X,表达式X:value_type通常指出了存储在容器中的值类型。对于关联容器来说,表达式X::key_type指出了键的类型。

关联容器的优点在于,它提供了对元素的快速访问。与序列相似,关联容器也允许插入新元素,但不能指定元素的插入位置。原因是关联容器通常有用于确定数据放置位置的算法,以便能够快速检索信息。

关联容器通常是使用某种树实现的。树是一种数据结构,其根节点链接到一个或两个节点,而这些节点又链接到一个或两个节点,从而形成分支结构。像链表一样,节点使得添加或删除数据项比较简单:但相对于链表,树的查找速度更快。

STL提供了4种关联容器: set、multiset、map和 multimap。前两种是在头文件set(以前分别为set.h和multiset.h)中定义的,而后两种是在头文件map(以前分别为map.h和 multimap.h)中定义的。

无序关联容器是对容器概念的另一种改进。与关联容器一样,无序关联容器也将值与键关联起来,并使用键来查找值。但底层的差别在于,关联容器是基于树结构的,而无序关联容器是基于数据结构哈希表的,这旨在提高添加和删除元素的速度以及提高查找算法的效率。有4种无序关联容器,它们是unordered_set、unordered_multiset、unordered_map和unordered_multimap。

5.算法库

STL包含很多处理容器的非成员函数,前面已经介绍过其中的一些: sort( )、copy( )、find( )、random_shuffle( )、 set_union( )、 set_intersection( )、set_difference()和 transform( )。可能已经注意到,它们的总体设计是相同的,都使用迭代器来标识要处理的数据区间和结果的放置位置。有些函数还接受一个函数对象参数,并使用它来处理数据。对于算法函数设计,有两个主要的通用部分。首先,它们都使用模板来提供泛型;其次,它们都使用迭代器来提供访问容器中数据的通用表示。因此,copy()函数可用于将double值存储在数组中的容器、将string 值存储在链表中的容器,也可用于将用户定义的对象存储在树结构中(如 set所使用的)的容器。因为指针是一种特殊的迭代器,因此诸如copy()等STL函数可用于常规数组。统一的容器设计使得不同类型的容器之间具有明显关系。例如,可以使用copy( )将常规数组中的值复制到vector对象中,将vector对象中的值复制到list对象中,将list对象中的值复制到set对象中。可以用==来比较不同类型的容器,如deque和vector。之所以能够这样做,是因为容器重载的==运算符使用迭代器来比较内容,因此如果deque对象和vector对象的内容相同,并且排列顺序也相同,则它们是相等的。

STL将算法库分成4组:非修改式序列操作;修改式序列操作;排序和相关操作;通用数字运算。前3组在头文件 algorithm(以前为algo.h)中描述,第4组是专用于数值数据的,有自己的头文件,称为numeric(以前它们也位于algol.h中)。

C++还提供了其他一些类库,它们比本章讨论前面的例子更为专用。例如,头文件complex为复数提供了类模板complex,包含用于float、long,和 long double的具体化。这个类提供了标准的复数运算及能够处理复数的标准函数。C++11新增的头文件random提供了更多的随机数功能。头文件valarray 提供的模板类valarray。这个类模板被设计成用于表示数值数组,支持各种数值数组操作,例如将两个数组的内容相加、对数组的每个元素应用数学函数以及对数组进行线性代数运算。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值