STL泛型编程(windows编程技术).doc

STL泛型编程(windows编程技术).doc

ID:49353167

大小:1.22 MB

页数:73页

时间:2020-02-29

上传者:U-25929
STL泛型编程(windows编程技术).doc_第1页
STL泛型编程(windows编程技术).doc_第2页
STL泛型编程(windows编程技术).doc_第3页
STL泛型编程(windows编程技术).doc_第4页
STL泛型编程(windows编程技术).doc_第5页
资源描述:

《STL泛型编程(windows编程技术).doc》由会员上传分享,免费在线阅读,更多相关内容在行业资料-天天文库

第三篇高级编程前两篇分别介绍了VisualC++的MFC编程和VisualC#的.NET编程的基础内容,重点放在用户界面和图形绘制部分。Windows编程技术非常广泛和丰富,许多内容属于高级课题,例如:系统编程、上下文帮助、动态链接库、组件编程、面向方面的编程、服务器端编程、分布式编程、云计算等等。限于篇幅,本书只简单介绍:企业级应用必须的泛型编程、基于多核CPU的并行编程、商业应用所需的数据库编程和应用广泛的网络编程。本篇包含如下4章内容:l第21章STL泛型编程l第22章多线程与多核编程l第23章数据库编程l第24章网络编程 第21章STL泛型编程泛型编程(genericprogramming,类属编程)是一种通过模板(template)实现同一算法源代码用于不同数据类型的软件重用方法,广泛用于数据的遍历、查询、排序、添加/删除/复制/合并等操作和处理,是数据库管理和信息系统等企业级应用不可缺少的基础技术。泛型编程理论的最早实现是AlexanderStepanov和DaveMusser于1994年设计和开发出的STL(StandardTemplateLibrary,标准模板库),它成为C++标准(1998年)的一部分。源自C++的Java(1995年)和C#(2000年)语言在创建时,为了简化语法和降低复杂度,都不约而同地抛弃了C++的模板,从而不支持泛型编程。但是事实很快就证明,这样做是犯了一个非常严重的错误。所以Java于2004年9月在JavaSE5.0(JDK1.5)中开始使用(受限的)泛型技术,C#和.NET框架也于2005年11月在2.0版中引入了泛型编程。限于篇幅,本书只讨论标准C++中的STL泛型编程的基本内容。21.1概述面向过程的编程重用函数的目标代码,面向对象的编程重用类和对象的目标代码,泛型编程则重用算法的源代码。传统C++的泛型编程,仅仅局限于简单的模版技术。标准C++引入了容器(container)、迭代器(iterator)、分配器(allocator)和STL等概念和内容,才真正进入泛型编程的广阔天地。21.1.1泛型编程与过程及对象编程泛型编程和面向过程以及面向对象一起,构成混合型程序设计语言C++的三种编程范式(paradigm,范例/范型)。面向过程的编程,可以将常用代码段封装在一个函数中,然后通过函数调用来达到目标代码重用的目的,是最早使用的软件重用方法。面向对象的方法,则可以通过类的继承和对象成员的使用来实现(对象的目标)代码的重用。如果需要编写一个可用于不同数据类型的算法,可以采用的方法有:l面向过程——对源代码进行复制和修改,生成 不同数据类型版本的算法函数,调用时需要对数据类型进行手工的判断。l面向对象——可以在一个类中,编写多个同名函数,它们的算法一致,但是所处理数据的类型不同,当然函数的输入参数类型也不同,可通过函数重载来自动调用对应数据类型版本的函数。显然,这两种方法都需编写了多个算法相同的不同函数,不能做到代码重用。它们二者之间的主要差别,只是调用的方便与否。如果采用泛型编程(例如可采用以类型作为参数的传统C++的模板技术),就可以做到源代码级的重用:l泛型编程——编写以类型作为参数的一个模板函数,在调用时再将参数实例化为具体的数据类型。注意,模版的实例化,是在编译阶段完成的,属于静态性质的方法,与C/C++的宏替代方式非常相似,但是却更为安全、更易理解、且更加高效。为了实现一个,在具有不同组织结构(如数组、链表、队列、堆栈等)、含同一类型(如char、int、float、struct或class等)的数据或对象的集合(容器)上的,与具体数据类型无关的参数化通用算法(如排序、检索、复制、合并等)。光有模版是远远不够的,还需要能够表示这种集合的容器、能够在容器中遍历的迭代器、能够为算法和容器实现抽象存储的分配器、能够在不同容器之间进行转换的适配器、以及针对容器中数据集合的各种与具体类型无关的通用算法等。这些正是泛型编程的研究内容,也是STL要实现的目标。21.1.2泛型编程与STL泛型编程是一种面向算法的多态技术,STL是它的一种具体实现。1.泛型编程在计算机科学中,泛型(generic,类属)是一种允许一个值取不同数据类型(所谓多态)的技术,强调使用这种技术的编程风格被称为泛型编程。泛型编程研究对软件组件的系统化组织,目标是推出一种针对算法、数据结构和内存分配机制的分类方法,以及其他能够带来高度可重用性、模块化和可用性的软件工具。 与针对问题和数据的面向对象的方法不同,泛型编程中强调的是算法。是一类通用的参数化算法,它们对各种数据类型和各种数据结构都能以相同的方式进行工作,从而实现源代码级的软件重用。例如,不管(容器)是数组、队列、链表、还是堆栈,不管里面的元素(类型)是字符、整数、浮点数、还是对象,都可以使用同样的(迭代器)方法来遍历容器内的所有元素、获取指定元素的值、添加或删除元素,从而实现排序、检索、复制、合并等各种操作和算法。泛型编程的通用化算法,是建立在各种抽象化基础之上的:利用参数化模版来达到数据类型的抽象化、利用容器和迭代器来达到数据结构的抽象化、利用分配器和适配器来达到存储分配和转换接口的抽象化。2.STLSTL(StandardTemplateLibrary,标准模板库)是泛型编程思想的实际体现和具体实现,它是一种为泛型组件建立大型标准库的可扩展架构。STL本身,与面向对象无关,也与具体的程序设计语言无关。STL的目标是,在不损失效率的基础上,进行抽象。这里的不损失效率,是指尽最大努力来保证其所有的泛型算法是最优的,并且和手工为具体类型编写的代码具有同样的运行效率。抽象的基础是数学逻辑和冯·诺依曼计算模型(存储程序体系结构)。如果用数学语言来描述,STL的本质就是:不同的数据结构,对应于不同的地址代数结构、以及不同的地址连接方式。从数据结构的一个地址,转向下一个地址的一些列操作,就对应于迭代器。在数据结构中添加和删除地址的操作,就对应于容器。STL将容器看作是结构的泛化,它们都拥有成员,可以描述整体和局部这一现实世界事物的关键属性。STL使用了赋值的算法,要求采用面向值的语义。STL还假定对容器中的数据和对象,定义了全序。STL的主要内容是容器、泛型算法、迭代器、函数对象、分配器和适配器等6种组件。在标准C++中,STL是作为C++标准库的一部分而出现的。作为STL的基础的模板,则是C++语法的一部分。3.历史泛型程序最早出现1970年代的CLU和Ada语言中,后来被许多基于对象和面向对象的语言所采用,包括BETA、C++、D和Eiffel等。1993年C++在3.0版中引入的模版技术就属于泛型编程的范畴,1994年7月ANSI/ISOC++标准委员会通过的STL, 更是泛型编程的集大成者,它已被纳入1998年9月推出的C++国际标准之中。VisualC++从2005版开始,全面支持C++的1998年和2003年国际标准,当然也包括STL泛型编程。Java于2004年9月在JavaSE5.0(JDK1.5)中开始使用泛型技术,并且因此将从JDK1.2~1.4一直使用的Java2改名为Java5,但是为了在语法、类库和字节码上与传统的Java2兼容,Java中的泛型做出了很多牺牲,包括类型信息被擦除。所以Java中的泛型是一种受限的非完整泛型。微软于2005年11月在.NET框架2.0版、C#2.0和VisualBasic2005中也于引入了泛型编程,并在2007年11月推出的.NET框架3.5版中又增加了对STL的支持。DavidMusserAlexanderStepanov1971年,美国计算机科学家DavidMusser首先提出并推广了泛型编程的理论,但是主要局限于软件开发和计算机代数领域。1979年,苏联籍的美国计算机科学家AlexanderStepanov开始研究泛型编程,1987年他与DavidMusser一道,开发出Ada的泛型表处理库。1990年前后,Stepanov先后在贝尔实验室和HP实验室,进行泛型编程的研究和库设计,提出了STL的体系结构。1993年11月,受贝尔实验室的AndrewKoening的邀请,Stepanov在ANSI/ISOC++标准委员会的会议上,介绍了泛型编程的理论和他们的工作。Stepanov和MengLee按委员会的要求,于1994年3月提出了STL的草案。委员会提出了一些修改意见,其中最重要的是对关联容器的扩充,由Musser完成了扩充部分的实现工作。1994年7月,ANSI/ISOC++标准委员会终于通过了修改后的STL方案,并将其纳入1998年推出的C++标准之中。1994年8月,HP将Stepanov和Lee等人开发的STL,在因特网上免费发布。1995年Stepanov又到SGI工作,他与HansBoehm和MatthewH.Austern一起,为SGI开发了STL。SGI也把它免费地放在了因特网上(http://www.sgi.com/tech/stl/download.html),并提供了一个完整的在线说明文档(由Austern编写,参见网页http://www.sgi.com/tech/stl/)。鉴于AlexanderStepanov对STL的重大贡献,他被誉为STL之父。21.2容器容器(container)是装有其他对象的对象。容器里面的对象必须是同一类型,该类型必须是可拷贝构造和可赋值的,包括内置的基本数据类型和带有公用拷贝构造函数和赋值操作符的类。典型的容器有队列、链表和向量等。 在标准C++中,容器一般用模版类来表示。不过STL不是面向对象的技术,不强调类的层次结构,而是以效率和实用作为追求的目标。所以在STL并没有一个通用的容器类,各种具体的容器也没有统一的基类。容器可以视为是数组的推广,即对象的数组(广义数组),其中的元素(对象)可以用下标(索引)来访问。容器的设计有两条准则:一是在各个容器的设计中,提供尽可能大的自由度;二是使各种容器能够向用户呈现出一个公共的界面/接口。目的是,使容器的实现能达到最佳效率,同时也使用户能写出不依赖于所使用的特定容器类型的通用代码。容器的设计通常只能满足这两条中的一条,但是STL却提供了一个同时具有通用性和执行效率的解决方案。21.2.1分类标准C++的STL框架中的主要容器有序列容器和关联容器两大类,另外两大类容器分别是容器适配器和拟容器。1.序列容器序列容器(sequencecontainer,顺序容器)将一组具有相同类型T的对象,以严格的线性形式组织在一起。序列容器可以视为数组和链表的推广。STL中的序列容器有如下3种:1)vector(向量)——提供对变长序列的快速随机访问(即对第i个元素的访问时间,是与i无关的常量),对序列末尾的插入和删除操作的时间是分摊常量;(似变长数组)(对应于vector类,定义在头文件中)。2)deque(double-endedqueue,双端队列)——提供对变长序列的快速随机访问,对序列头尾的插入和删除操作的时间都是分摊常量;(似双向变长数组)(对应于deque类,定义在头文件中)。3)list(表)——提供对变长序列的线性时间访问(O(N),N为序列的当前长度),但是在序列的任意位置的插入和删除操作均为常量时间。(似双向链表)(对应于list类,定义在头文件中)。 2.关联容器关联容器(associativecontainer,联合容器)的特点是(键)有序的,即元素是按预定义的键顺序(如升序)插入的。关联容器具有从基于键的集合中快速提取对象的能力,其中集合的大小在运行时是可变的。关联容器可以视为关联数组、映射或字典的推广,它们保存的都是值的对偶,给定了其中的一个被称为键(key)的值,就可以快速访问与其对偶的另一个被称为映射值(mappedvalue)的值。STL中的关联容器有如下4种:1)set(集合)——支持唯一键值,并提供对键本身的快速检索;例如set:{学号}。集合关联容器,对应于set类,定义在头文件中。2)multiset(多重集合)——支持可重复键值,并提供对键本身的快速检索;例如set:{姓名}(可能有同名的)。多重集合关联容器,对应于multiset类,也定义在头文件中。3)map(映射/映像)——支持唯一Key类型的键值,并提供对另一个基于键的类型T的快速检索;例如map:{(学号,姓名)}、{(电话号码,姓名)}等。映射关联容器,对应于map类,定义在头文件中。4)multimap(多重映射)——支持可重复Key类型的键值,并提供对另一个基于键的类型T的快速检索;例如map:{(姓名,地址)}、{(姓名,年龄)}等。多重映射关联容器,对应于multimap类,也定义在头文件中。为了改进搜索的时间,微软还在STL的VC实现中,增加了4种对应的散列(hash)关联容器类型:1)hash_set(散列集),对应于hash_set类,定义在头文件中。2)hash_multiset(散列多集),对应于hash_multiset类,也定义在头文件中。3)hash_map(散列映射),对应于hash_map类,定义在头文件中。4)hash_multimap(散列多映射),对应于hash_multimap类,也定义在头文件中。 3.容器适配器容器适配器(containeradapter)不是独立的容器,只是某种容器的变种,它提供原容器的一个专用的受限接口。特别是,容器适配器不提供迭代器。在STL中有如下3种容器适配器:1)stack(栈)——只支持top()(读取栈顶元素)、push()(在栈顶处加入新元素)和pop()(取出栈顶元素)操作(先入后出)的一种序列容器。可以是对任意一种序列容器(默认为双端队列deque)的限制实现:删除非栈操作,将原来序列容器的标准操作back()、push_back()和pop_back()重新命名为top()、push()和pop()。栈容器适配器,对应于stack类,定义在头文件中。2)queue(队列)——与stack类似,queue也是对序列容器(默认也为双端队列deque)的限制实现。与栈相比,队列也支持back()(读取队尾处的元素)和push_back()(在队尾处插入新元素)操作,但是不再支持pop_back()(取出队尾处的元素)操作。不过,队列却允许front()(读取队首处的元素)和pop_front()(取出队首处的元素)操作(前出后入)。由于矢量vector容器不支持pop_front()操作,所以不能作为队列queue的基础容器。与前后都可出入的双端队列deque相比,队列queue缺少push_front()和pop_back()操作。队列容器适配器,对应于queue类,定义在头文件中。3)priority_queue(优先队列)——也是一种队列queue,不过其中的每个元素都被给定了一个优先级,用来控制元素到达队首top()的顺序。默认情况下,优先队列简单地使用运算符<进行元素比较,top()返回最大的元素。注意,优先队列,并不要求其全部元素都是有序的,而只要求其第一个元素是最大的。优先队列容器适配器,对应于priority_queue类,也定义在头文件中。4.拟容器拟容器(almostcontainer,似容器)在许多情况下可视为容器,但是又缺乏标准容器接口的某些方面和特性,不能与开发完全的容器进行全面互换。除了内置数组外,STL中的拟容器有如下三3种:1)string(串)—— 是实例化模版类basic_string的类型定义typedef(另一个常用的wstring类,则是实例化模版类basic_string的类型定义typedef)。基本串basic_string提供下标操作、随机访问迭代器和其他序列容器的几乎所有功能,但是它不像容器那样支持广泛的元素类型选择,而且它还为作为字符串使用进行了优化,所以其典型使用方式与容器有着显著差异。串拟容器,对应于string类,定义在头文件中。有关string和wstring类的更详细内容,会在后面的21.6节中介绍。1)valarray(值数组)——是为数值计算而进行了优化的向量,并不是一个具有通用性的容器。valarray提供了许多有用的数值运算和常用的数学函数,但是它只提供了标准容器操作中的size()和下标操作,此外,其中元素的指针是一种随机迭代器。值数组拟容器,对应于valarray类,定义在头文件中。2)bitset(位集)——是标志位字段的扩展,它通过提供在N个二进制位的集合(下标0~N-1)上的各种操作,使对标志位的运算更为方便。bitset可视为一个N位的二进制数,位取值0/1代表真假或开关,每一位从低位向高位进行编号。位集拟容器,对应于bitset类,定义在头文件中。5.总结比较我们将STL的各种容器及其性能罗列于表21-1中。表21-1STL容器类型容器访问方式添加元素删除元素说明序列容器向量vector随机尾尾似变长数组双端队列deque随机头尾头尾似双向变长数组表list线形任意位置任意位置似双向链表关联容器集合set随机顺序移复唯一健多重集合multiset随机顺序移复可多健映射map随机顺序移复唯一健多重映射multimap随机顺序移复可多健容器适配器栈stack尾(栈顶)尾(栈顶)尾(栈顶)默认基deque队列queue头尾尾头默认基deque优先队列priority_queue头尾尾头默认基deque头部的值最大 拟容器内置数组a[N]随机不能不能只支持内置基本数据类型串string和wstring随机任意位置任意位置是basic_string对char和wchar_t类型的实例化值数组valarray随机不能不能适合数值计算位集bitset随机不能不能N个bool标志位其中,“移复”是指,通过对被删元素后面的元素进行前移复制,来完成删除操作。由于篇幅的限制,不能对每个容器都作详细介绍。只是从中挑选几个典型的容器,在下面的几个小节中进行具体的讲解。21.2.2vector向量容器vector是STL中最简单且最典型的一个。它在标准C++里,是一个定义在命名空间std中的模版类(由头文件给出)。1.定义namespacestd{//取自C++2003标准(节选)template>classvector{public:……//types:类型typedefimplementationdefinediterator;//迭代器T*typedefTvalue_type;//元素类型typedefAllocatorallocator_type;//分配器类型typedefstd::reverse_iteratorreverse_iterator;//反向迭代器explicitvector(constAllocator&=Allocator());//构造函数˜vector();//析构函数//iterators:迭代器iteratorbegin();//指向首元素 iteratorend();//指向尾元素后的一个位置reverse_iteratorrbegin();//指向反向序列的首元素reverse_iteratorrend();//指向反向序列尾元素后的一个位置//capacity:容量size_typesize()const;//元素个数boolempty()const;//清空//elementaccess:元素访问referenceoperator[](size_typen);//下标运算符重载(不带检查的访问)referenceat(size_typen);//带检查的访问referencefront();//首元素referenceback();//尾元素//modifiers:修改方法//堆栈操作:voidpush_back(constT&x);//在尾部加入元素voidpop_back();//从尾部删除元素//表操作:iteratorinsert(iteratorposition,constT&x);//在指定位置插入元素iteratorerase(iteratorposition);//删除指定位置的元素voidclear();//删除所有元素……};//比较运算符重载:==、<、!=、>、>=、<=、、、template//相等booloperator==(constvector&x,constvector&y);template//小于booloperator<(constvector&x,constvector&y);……//specializedalgorithms:专用算法template//交换向量x与向量y的句柄voidswap(vector&x,vector&y); }在C++的标准库中,还专门化了一个布尔型向量的模版类(带有分配器参数):templateclassstd::vector;2.图书评级例下面是一个使用向量容器给图书评级的例子://Review.cpp#include#include#includeusingnamespacestd;structReview{//图书评价结构stringtitle;//书名intrating;//等级};boolFillReview(Review&rv);voidShowReview(constReview&rv);intmain(){vectorbooks;Reviewrv;while(FillReview(rv))books.push_back(rv);cout<<" 谢谢。你的输入如下: 评级t图书 ";//手工循环intnum=books.size();for(inti=0;i::iteratorpr;for(pr=books.begin();pr!=books.end();pr++) ShowReview(*pr);vectoroldrv(books);//使用拷贝构造函数//删加元素if(num>3){//删除2个元素books.erase(books.begin()+1,books.begin()+3);cout<<" 删除后: ";for(pr=books.begin();pr!=books.end();pr++)ShowReview(*pr);books.insert(books.begin(),oldrv.begin()+1,oldrv.begin()+2);//插入1个元素cout<<" 插入后: ";for(pr=books.begin();pr!=books.end();pr++)ShowReview(*pr);}books.swap(oldrv);//交换向量cout<<" 交换向量后: ";for(pr=books.begin();pr!=books.end();pr++)ShowReview(*pr);return0;}boolFillReview(Review&rv){cout<<"输入书名(quit终止):";getline(cin,rv.title);if(rv.title=="quit")returnfalse;cout<<"输入评级(0~10):";cin>>rv.rating;if(!cin)returnfalse;cin.get();returntrue; }voidShowReview(constReview&rv){cout<头文件(节选)template>classstack{ public:typedeftypenameContainer::value_typevalue_type;//值类型typedeftypenameContainer::size_typesize_type;//大小类型typedefContainercontainer_type;//容器类型protected:Containerc;//容器public:explicitstack(constContainer&=Container());//构造函数boolempty()const{returnc.empty();}//清空size_typesize()const{returnc.size();}//大小value_type&top(){returnc.back();}//读取栈顶元素constvalue_type&top()const{returnc.back();}//读取栈顶元素voidpush(constvalue_type&x){c.push_back(x);}//压栈voidpop(){c.pop_back();}//出栈};//比较运算符重载:……}当然,你也可以从对其他序列容器的限制来得到栈。2.逆序输出代码行例下面是一个将代码文件的文本内容逐行压入栈顶,然后再逐行显示并弹出栈顶元素的例子,其中灰色部分为其他基容器版本的栈://Stack.cpp#include#include#include#include//#include //#includeusingnamespacestd;//Rearrangecommentsbelowtousedifferentversions.typedefstackStack1;//Default:deque//typedefstack>Stack2;//typedefstack>Stack3;intmain(){ifstreamin("Stack.cpp");Stack1textlines;//Trythedifferentversions//Stack2textlines;//Version2//Stack3textlines;//Version3//Readfileandstorelinesinthestack:stringline;while(getline(in,line))textlines.push(line+" ");//Printlinesfromthestackandpopthem:while(!textlines.empty()){cout<头文件中。映射:键→值,值=映射[键](似f:x→y,y=f(x))。即,可以通过映射,由键来快速定位值。 图21-2逆序输出代码文件行1.定义namespacestd{//取自C++2003标准(节选)template,classAllocator=allocator>>classmap{public:节选//types:类型typedefKeykey_type;typedefTmapped_type;typedefpairvalue_type;……//mapoperations:映射操作iteratorfind(constkey_type&x);……pairequal_range(constkey_type&x);pairequal_range(constkey_type&x)const;}; //比较运算符重载:templatebooloperator==(constmap&x,constmap&y);……}2.pairmap容器的值类型是pair:typedefpairvalue_type;其中的pair是标准C++定义在头文件中的模版结构:templatestructpair{typedefType1first_type;typedefType2second_typeType1first;Type2second;pair();pair(constType1&__Val1,constType2&__Val2);templatepair(constpair&_Right);};3.单词计数例为了显示映射容器的威力,我们举一个单词计数程序的例子。此处的单词是以白空符分隔的广义字符串。注意,我们在下面的代码中(特别是在标点符号和运算符的两边),人为增加了许多空格符://WordCount.cpp#include#include#include#include usingnamespacestd;intmain(intargc,char*argv[]){typedefmapWordMap;//定义特定的单词映射类型typedefWordMap::iteratorwmIter;//定义该类型的迭代器constchar*fname="WordCount.cpp";//默认文件名串if(argc>1)fname=argv[1];//读入命令行的第一个参数,作为文件名路径串ifstreamin(fname);//打开文件输入流if(!in){//如果打开错误,则显示提示信息后退出cout<<"Openfile"<>word)wordmap[word]++;,//遍历容器,显示输出计数大于等于3的单词和计数for(wmIterw=wordmap.begin();w!=wordmap.end();w++)if(w->second>=3)cout<first<<":"<second<)是按单词的ASCII码字典顺序排列的。程序中的wordmap[word]++;对(将int与word关联在一起的映射值)表达式进行增1操作。如果映射中没有单词word,则作为该单词的键-值对(此时的值被初始化为0),就会被自动加入到映射中(这是map容器所具有的零初始化能力);如果word已经在映射中,则该表达式会将键所对应的值加1。图21-3单词计数你也可以用下面的语句,来单独显示某个单词键(例如单词字符串“wordmap”)的计数值:cout<<"wordmap:"<头文件内。在标准C++中,一共定义了66种泛型算法,它们都是以模版函数的形式给出的。这些函数都是用来处理容器内容的非成员函数,它们都使用模版来提供通用类型,使用迭代器来提供对容器中数据的通用访问(用迭代器来标识要处理的数据范围和结果存放的位置),有些函数还接受函数对象参数,并用其来处理数据。下面是C++标准库中的算法头文件的内容:namespacestd{//取自C++2003标准(节选)//non-modifyingsequenceoperations:非修改性序列算法template Functionfor_each(InputIteratorfirst,InputIteratorlast,Functionf);templateInputIteratorfind(InputIteratorfirst,InputIteratorlast,constT&value);templatetypenameiterator_traits::difference_typecount(InputIteratorfirst,InputIteratorlast,constT&value);templateForwardIterator1search(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2);//modifyingsequenceoperations:修改性序列算法//copy:复制templateOutputIteratorcopy(InputIteratorfirst,InputIteratorlast,OutputIteratorresult);//swap:交换templatevoidswap(T&a,T&b);templateOutputIteratortransform(InputIteratorfirst,InputIteratorlast,OutputIteratorresult,UnaryOperationop);templatevoidreplace(ForwardIteratorfirst,ForwardIteratorlast,constT&old_value,constT&new_value);templatevoidfill(ForwardIteratorfirst,ForwardIteratorlast,constT&value);templateForwardIteratorremove(ForwardIteratorfirst,ForwardIteratorlast,constT&value);templatevoidreverse(BidirectionalIteratorfirst,BidirectionalIteratorlast);//partitions:划分templateBidirectionalIteratorpartition(BidirectionalIteratorfirst,BidirectionalIteratorlast,Predicatepred);//sortingandrelatedoperations:排序与相关运算//sorting:排序 templatevoidsort(RandomAccessIteratorfirst,RandomAccessIteratorlast);templateboolbinary_search(ForwardIteratorfirst,ForwardIteratorlast,constT&value);//merge:归并templateOutputIteratormerge(InputIterator1first1,InputIterator1last1,InputIterator2first2,InputIterator2last2,OutputIteratorresult);//setoperations:集合运算templateboolincludes(InputIterator1first1,InputIterator1last1,InputIterator2first2,InputIterator2last2);……//heapoperations:堆运算templatevoidmake_heap(RandomAccessIteratorfirst,RandomAccessIteratorlast);templatevoidsort_heap(RandomAccessIteratorfirst,RandomAccessIteratorlast);templatevoidsort_heap(RandomAccessIteratorfirst,RandomAccessIteratorlast,Comparecomp);//minimumandmaximum:最小与最大templateconstT&min(constT&a,constT&b);templateconstT&max(constT&a,constT&b);//permutations:排列templateboolnext_permutation(BidirectionalIteratorfirst,BidirectionalIteratorlast);templateboolprev_permutation(BidirectionalIteratorfirst,BidirectionalIteratorlast);} 21.3.2分类算法函数表可以按是否修改容器中的内容和排序操作,来给标准库中的66种算法函数进行分类,参见表21-2~4。表21-2非修改性序列操作(12个)类型函数名说明循环for_each()对序列中的每个元素执行某操作。查找find()在序列中找出某个值的第一次出现的位置。find_if()在序列中找出符合某谓词的第一个元素。find_end()在序列中找出一子序列的最后一次出现的位置。find_first_of()在序列中找出第一次出现指定值集中之值的位置。adjacent_find()在序列中找出相邻的一对值。计数count()在序列中统计某个值出现的次数。count_if()在序列中统计与某谓词匹配的次数。比较mismatch()找出两个序列相异的第一个元素。表21-3修改性序列操作(27个)类型函数名说明复制copy()从序列的第一个元素起进行复制。copy_backward()从序列的最后一个元素起进行复制。交换swap()交换两个元素。swap_ranges()交换指定范围的元素。iter_swap()交换由迭代器所指的两个元素。变换transform()将某操作应用于指定范围的每个元素。替换replace()用一个给定值替换一些值。replace_if()替换满足谓词的一些元素。replace_copy()复制序列时用一给定值替换元素。replace_copy_if()复制序列时替换满足谓词的元素。填充fill()用一给定值取代所有元素。fill_n()用一给定值取代前n个元素。 生成generate()用一操作的结果取代所有元素。generate_n()用一操作的结果取代前n个元素。删除remove()删除具有给定值的元素。remove_if()删除满足谓词的元素。remove_copy()复制序列时删除具有给定值的元素。remove_copy_if()复制序列时删除满足谓词的元素。唯一unique()删除相邻的重复元素。unique_copy()复制序列时删除相邻的重复元素。反转reverse()反转元素的次序。reverse_copy()复制序列时反转元素的次序。环移rotate()循环移动元素。rotate_copy()复制序列时循环移动元素。随机random_shuffle()采用均匀分布来随机移动元素。划分partition()将满足某谓词的元素都放到前面。stable_partition()将满足某谓词的元素都放到前面并维持原顺序。表21-4序列排序及相关操作(27个)类型函数名说明排序sort()以很好的平均效率排序。stable_sort()排序,并维持相同元素的原有顺序。partial_sort()将序列的前一部分排好序。partial_sort_copy()复制的同时将序列的前一部分排好序。第n个元素nth_element()将第n各元素放到它的正确位置。二分检索lower_bound()找到大于等于某值的第一次出现。upper_bound()找到大于某值的第一次出现。equal_range()找到(在不破坏顺序的前提下)可插入给定值的最大范围。binary_search()在有序序列中确定给定元素是否存在。归并merge()归并两个有序序列。 inplace_merge()归并两个接续的有序序列。有序结构上的集合操作includes()一序列为另一序列的子序列时为真。set_union()构造两个集合的有序并集。set_intersection()构造两个集合的有序交集。set_difference()构造两个集合的有序差集。set_symmetric_difference()构造两个集合的有序对称差集(并-交)。堆操作push_heap()向堆中加入元素。pop_heap()从堆中弹出元素。make_heap()从序列构造堆。sort_heap()给堆排序。最大和最小min()两个值中较小的。max()两个值中较大的。min_element()序列中的最小元素。max_element()序列中的最大元素。词典比较lexicographical_compare()两个序列按字典序的第一个在前。排列生成器next_permutation()按字典序的下一个排列。prev_permutation()按字典序的前一个排列。21.3.3函数对象函数对象(functionobject)也被称为函数符(functor,函数子/函数符)或拟函数对象(function-likeobject,类函数对象),可用作算法函数的参数,来处理容器中的数据。函数对象可以是函数名、指向函数的指针、重载了()运算符的类对象(即定义了operator()()函数的类的实例)。例如,算法for_each:templateFunctionfor_each(InputIteratorfirst,InputIteratorlast,Functionf);的例子:for_each(books.begin(),books.end(),ShowReview);在STL定义了若干函数符概念(种类):l生成器(generator)——是不用参数就可以调用的函数对象。 l一元函数(unaryfunction)——是用一个参数调用的函数对象。n谓词(predicate判定)——返回bool值的一元函数。l二元函数(binaryfunction)——是用两个参数调用的函数对象。n二元谓词(binarypredicate二元判定)——返回bool值的二元函数。在C++标准库中,定义若干标准函数对象(包括各种运算符,参见表21-5),它们都位于头文件中:namespacestd{//取自C++2003标准(节选)//base:基础(函数对象的基类)templatestructunary_function;templatestructbinary_function;//arithmeticoperations:算术运算templatestructplus;……//comparisons:比较templatestructequal_to;……//logicaloperations:逻辑运算templatestructlogical_and;……//negators:否定器templatestructunary_negate;……//binders:绑定器templateclassbinder1st;……//adaptors:适配器templateclasspointer_to_unary_function;……}表21-5操作符与对应的函数对象(15个)种类操作符函数对象名称种类操作符函数对象名称算术运算+plus加谓词>greater大于-minus减=greater_equal大于等于/divides除<=less_equal小于等于 %modulus模&&logical_and逻辑与-negate负||logical_or逻辑或谓词==equal_to等于!logical_not逻辑非!=not_equal_to不等于21.3.4适配器STL为支持函数对象的组合,提供了各种适配器(adaptor)函数对象(参见表21-6):l绑定器(binder)——通过将一个参数绑定到某个值,可将两个参数的函数对象当作一个参数的函数对象使用。l成员函数适配器(memberfunctionadaptor)——使成员函数可以用作算法的参数。l函数指针适配器(functionpointeradaptor)——使函数指针可以用作算法的参数。l否定器(negater)——使能够描述某个谓词的否定。这些适配器有统一的结构,它们都依赖于函数对象的基类unary_function和binary_function。STL还为每个适配器提供了一个辅助函数,它以一个函数对象为参数,返回另一个合适的函数对象。在被这个函数对象的operator()()调用时,该函数对象能完成所需的动作。换句话说,这种适配器,是一种简单的高阶函数,它以一个函数为参数,并产生另一个新的函数。表21-6适配器(14个)种类适配器类功能绑定器bind1st(x)binder1st以x为第一个参数调用二元函数bind2nd(y)binder2nd以y为第二个参数调用二元函数成员函数适配器mem_fun()mem_fun_t通过指针调用0元成员函数mem_fun1_t通过指针调用一元成员函数const_mem_fun_t通过指针调用0元常型成员函数const_mem_fun1_t通过指针调用一元常型成员函数mem_fun_ref()mem_fun_ref_t通过引用调用0元成员函数mem_fun1_ref_t通过引用调用一元成员函数const_mem_fun_ref_t通过引用调用0元常型成员函数 const_mem_fun1_ref_t通过引用调用一元常型成员函数函数指针适配器ptr_fun()pointer_to_unary_function调用一元函数指针pointer_to_binary_function调用二元函数指针否定器not1()unary_negate否定一元谓词not2()binary_negate否定二元谓词在STL中,各类函数符概念都有对应的自适应(adaptable)版本,例如自适应生成器(adaptablegenerator)和自适应二元断言(adaptablebinarypredicate)等。自适应的函数符,携带了标识参数类型和返回值类型的typedef(类型定义)成员:first_argument_type、second_argument_type、……、result_type。例如,plus对象的返回值类型,被标识为plus::result_type,它是int的typedef。函数对象自适应的意义在于,函数适配器(functionadaptor)对象可以使用函数对象,并认为存在这些typedef成员。STL中提供了使用这些函数适配器的工具——指向函数适配器的指针:templateclasspointer_to_unary_function;templatepointer_to_unary_functionptr_fun(Result(*)(Arg));templateclasspointer_to_binary_function;templatepointer_to_binary_functionptr_fun(Result(*)(Arg1,Arg2));一般的算法并不关心其函数参数是函数、函数指针、还是函数对象,但是绑定器需要保存函数的一个副本供以后使用。所以STL才在里提供了上面这两个适配器,使函数指针也可以与标准算法一起使用。例如(输出结果见图21-4)://FunAdap.cpp#include#include图21-4函数适配器例#include#include#include usingnamespacestd;voidShow(doublev);constintLIM=5;intmain(){doublea1[LIM]={36,39,42,45,48};doublea2[LIM]={25,27,29,31,33};vectorg(a1,a1+LIM);vectorm(a2,a2+LIM);cout<<"g:t";for_each(g.begin(),g.end(),Show);cout<sum(LIM);transform(g.begin(),g.end(),m.begin(),sum.begin(),plus());cout<<"sum:t";for_each(sum.begin(),sum.end(),Show);cout<prod(LIM);transform(g.begin(),g.end(),prod.begin(),bind1st(multiplies(),2.5));cout<<"prod:t";for_each(prod.begin(),prod.end(),Show);cout<Functionfor_each(InputIteratorfirst,InputIteratorlast,Functionf);该函数的定义为:templateOpfor_each(Infirst,Inlast,Opf){while(first!=last)f(*first++);returnf;}for_each算法用来代替显式循环,对从序列first处的元素一直到last之前的元素,都执行f操作。f操作可以可以修改序列的内容。2.查找(find)查找算法(find_end除外)从头至尾查看一个(或一对)序列,寻找与一个值或谓词匹配的元素。一般,所有算法的谓词版本,都有_if后缀。STL中一共有5种查找算法函数(参见表21-7),其中有3个还各有一个重载版本(都是多一个输入参数BinaryPredicatepred),所以共有8个模板函数,下面是它们的函数原型:表21-7查找(元素值)算法(5-1个)功能算法说明值查找find()在序列中找出某个值的第一次出现的位置。谓词查找find_if()在序列中找出符合某谓词的第一个元素。子列查找find_end()在序列中找出一子序列的最后一次出现的位置。集值查找find_first_of()在序列中找出第一次出现指定值集中之值的位置。邻对查找adjacent_find()在序列中找出相邻的一对值。templateInputIteratorfind(InputIteratorfirst,InputIteratorlast,constT&value);templateInputIteratorfind_if(InputIteratorfirst, InputIteratorlast,Predicatepred);templateForwardIterator1find_end(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2);templateForwardIterator1find_end(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2,BinaryPredicatepred);templateForwardIterator1find_first_of(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2);templateForwardIterator1find_first_of(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2,BinaryPredicatepred);templateForwardIteratoradjacent_find(ForwardIteratorfirst,ForwardIteratorlast);templateForwardIteratoradjacent_find(ForwardIteratorfirst,ForwardIteratorlast,BinaryPredicatepred);3.搜索(search)搜索算法在一个序列中寻找一个子序列。STL中有3种搜索算法函数(包括子列查找算法find_end,参见表21-8):search、search_n和find_end,它们分别用于寻找子序列的第一次、(由连续n个指定值构成的同值子序列的)第一次和最后一次出现。它们各有一个重载版本(都是多一个输入参数BinaryPredicatepred),所以(不算前面已交给出的两个find_end函数)共有4个模板函数,下面是它们的函数原型:表21-8搜索(子序列)算法(2+1个)功能算法说明搜索子列search()在序列中找出一子序列的第一次出现的位置。搜索重复子列search_n()在序列中找出一值的连续n次出现的位置。查找子列find_end()在序列中找出一子序列的最后一次出现的位置。 templateForwardIterator1search(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2);templateForwardIterator1search(ForwardIterator1first1,ForwardIterator1last1,ForwardIterator2first2,ForwardIterator2last2,BinaryPredicatepred);templateForwardIteratorsearch_n(ForwardIteratorfirst,ForwardIteratorlast,Sizecount,constT&value);templateForwardIterator1search_n(ForwardIteratorfirst,ForwardIteratorlast,Sizecount,constT&value,BinaryPredicatepred);4.例子下面是一个使用非修改性序列算法的例子(输出结果见图21-5)://SearchReplace.cpp#include#include#include#includeusingnamespacestd;template//显示输出函数voidprint(Iterfirst,Iterlast,constchar*nm="",constchar*sep=" ",ostream&os=cout){if(nm!=0&&*nm!='')os<::value_typeT;copy(first,last,ostream_iterator(cout,sep));os<对象值时返回真intvalue;public:MulMoreThan(intval):value(val){}booloperator()(intv,intm){returnv*m>value;}};intmain(){//主函数inta[]={1,2,3,4,5,6,6,7,7,7,8,8,8,8,11,11,11,11,11};//整数数组aconstintASZ=sizeofa/sizeof*a;//a中的元素个数vectorv(a,a+ASZ);//整数向量(=a)print(v.begin(),v.end(),"v","");//显示输出//查找整数4vector::iteratorit=find(v.begin(),v.end(),4);cout<<"find:"<<*it<8的数it=find_if(v.begin(),v.end(),bind2nd(greater(),8));cout<<"find_if:"<<*it<v2;//向量2//替换复制:将8换成47replace_copy(v.begin(),v.end(),back_inserter(v2),8,47);//输出结果print(v2.begin(),v2.end(),"replace_copy8->47","");//谓词替换:将>=7的数都换成-1replace_if(v.begin(),v.end(),ind2nd(greater_equal(),7),-1);//输出结果print(v.begin(),v.end(),"replace_if>=7->-1","");}21.3.6修改性序列算法提供修改性算法(mutatingsequenceoperations)是为了完成生成和更新等操作。 标准算法,是通过迭代器在数据结构上进行工作的。这就意味着,要对序列插入和删除一个元素,都是很不容易的。因为,除非使用特殊的迭代器(如插入器),不然,靠普通的迭代器是不能改变容器大小的。图21-5非修改性序列算法的例子不过,只要不插入和删除元素,还是可以通过迭代器来修改元素的值、交换元素、复制元素等。甚至,remove(移去)或erase(擦除)等删除操作,也可以通过复写掉一些元素(将后面的元素前移复制)来完成。总之,基本的修改操作所产生的输出,是其输入序列修改后的副本。那些看似真正的修改算法,其实只是一些变形,它们是在序列内做复制操作。1.复制(copy)复制是由一个序列产生另一个序列的最简单方式。STL中定义了2个复制算法函数(参见表21-9),下面是它们的函数原型:表21-9复制算法(2个)功能算法说明前向复制copy()从序列的第一个元素起进行复制。后向复制copy_backward()从序列的最后一个元素起进行复制。templateOutputIteratorcopy(InputIteratorfirst,InputIteratorlast,OutputIteratorresult); templateBidirectionalIterator2copy_backward(BidirectionalIterator1first,BidirectionalIterator1last,BidirectionalIterator2result);复制算法的目标,不必是容器,可以是任何能用输出迭代器描述的东西,如cout。因为输入和输出可能会重叠,为了避免在复制时,覆盖会改写原数据,可以采用反向复制copy_backward()方式。这种方式,直到复制之时,元素是不会被覆盖的。不过,使用反向复制,要求输入和输出序列,都有双向迭代器。2.变换(transform)变换并不修改其输入序列,而只是基于用户提供的操作,对输入进行某种变换,从而生成一个输出序列。STL中只有一种变换算法transform,有两个重载函数,后一个多了一个InputIterator2first2输入参数,而且最后一个输入参数的类型也不同:templateOutputIteratortransform(InputIteratorfirst,InputIteratorlast,OutputIteratorresult,UnaryOperationop);templateOutputIteratortransform(InputIterator1first1,InputIterator1last1,InputIterator2first2,OutputIteratorresult,BinaryOperationbinary_op);例如(用到了函数指针适配器,输出结果见图21-6)://PtrFun.cpp#include#include#include图21-6复制与变换算法例#include#include#includeusingnamespacestd;boolisEven(intx){returnx%2==0;}intmain(){ inta[]={123,94,10,314,315};constintASZ=sizeofa/sizeof*a;vectorvb;transform(a,a+ASZ,back_inserter(vb),not1(ptr_fun(isEven)));copy(vb.begin(),vb.end(),ostream_iterator(cout,""));cout<vd;transform(d,d+DSZ,back_inserter(vd),bind2nd(ptr_fun(pow),2.0));copy(vd.begin(),vd.end(),ostream_iterator(cout,""));cout<voidreplace(ForwardIteratorfirst,ForwardIteratorlast,constT&old_value,constT&new_value);templatevoidreplace_if(ForwardIteratorfirst,ForwardIteratorlast,Predicatepred,constT&new_value);templateOutputIteratorreplace_copy (InputIteratorfirst,InputIteratorlast,OutputIteratorresult,constT&old_value,constT&new_value);templateOutputIteratorreplace_copy_if(Iteratorfirst,Iteratorlast,OutputIteratorresult,Predicatepred,constT&new_value);21.3.7排序及相关算法排序(sorting)是最常用的算法之一,因为对有序序列,可进行很多高效且方便的操作。要对一个序列进行排序,必须有一种比较元素的方法,可通过二元谓词来实现。默认的比较是less,它默认使用运算符<来进行元素的比较。排序及相关算法(sortingandrelatedoperations)包括排序、二分搜索、归并和堆操作。1.排序(sort)排序算法需要随机访问迭代器,最好用向量容器vector和双端队列容器deque,或类似的容器。标准的表容器list,没有提供随机访问迭代器,所以,对list的排序,应该使用list类中所提供的sort成员函数,而不是用这里所讨论的std中的标准算法。STL中提供了4种排序算法(参见表21-11),它们各有两个重载函数,都是第二个多一个输入参数Comparecomp。下面是部分排序函数的原型(其余的类似):表21-11排序算法(4个)功能算法说明排序sort()以很好的平均效率排序。稳定排序stable_sort()排序,并维持相同元素的原有顺序。部分排序partial_sort()将序列的前一部分排好序。复制部分排序partial_sort_copy()复制的同时将序列的前一部分排好序。templatevoidsort(RandomAccessIteratorfirst,RandomAccessIteratorlast);templatevoidstable_sort(RandomAccessIteratorfirst,RandomAccessIteratorlast,Comparecomp);templatevoidpartial_sort(RandomAccessIteratorfirst, RandomAccessIteratormiddle,RandomAccessIteratorlast);templateRandomAccessIteratorpartial_sort_copy(InputIteratorfirst,InputIteratorlast,RandomAccessIteratorresult_first,RandomAccessIteratorresult_last,Comparecomp);其中的基本排序算法sort的效率很高:平均为O(N*logN)、最坏为O(N*N)。为了避免最坏的情况,可以使用稳定排序stable_sort,它的算法复杂度为O(N*log2N),若系统的存储区足够大,其性能可改善为趋向O(N*logN)。如果只需对序列的前一部分进行排序,可以采用部分排序算法partial_sort,它只对first到middle的元素进行排序。partial_sort_copy算法将输入序列排好序后复制到输出序列中,如果输出序列较短,则截断排好序的序列。例如(对前vector例排序,补充运算符<的重载,输出结果见图21-7)://SortBooks.cpp#include#include#include#includeusingnamespacestd;structReview{//图书评论结构stringtitle;//书名intrating;//等级//重载books; Reviewrv;//接受用户输入并显示while(FillReview(rv))books.push_back(rv);cout<<" 谢谢。你的输入如下: 评级t图书 ";for_each(books.begin(),books.end(),ShowReview);//按评级排序并显示sort(books.begin(),books.end());//使用重载的ForwardIteratorlower_bound(ForwardIteratorfirst,ForwardIteratorlast,constT&value);templateForwardIteratorlower_bound(ForwardIteratorfirst,ForwardIteratorlast,constT&value,Comparecomp);templatepairequal_range(ForwardIteratorfirst,ForwardIteratorlast,constT&value);3.归并(merge)可以使用merge(归并/合并)算法,将两个有序序列合并在一起,生成一个新的有序序列。也可以使用inplace_merge(内部拼接)算法,将一个序列的两个部分合并在一起。STL中提供了有序序列的两个归并算法(参见表21-13),每个算法有两个重载函数(共4个函数),也是第二个重载函数多一个Comparecomp输入参数。下面是它们的部分函数原型(其余的类似):表21-13归并算法(2个)功能算法说明 合并merge()归并两个有序序列。拼接inplace_merge()归并两个接续的有序序列。templateOutputIteratormerge(InputIterator1first1,InputIterator1last1,InputIterator2first2,InputIterator2last2,OutputIteratorresult);templatevoidinplace_merge(BidirectionalIteratorfirst,BidirectionalIteratormiddle,BidirectionalIteratorlast,Comparecomp);4.堆(heap)术语堆(heap),在不同的环境中有不同的含义。泛型算法中的堆,通常是指一种组织序列元素的方式:l第一个元素*first最大(注意,这里并不要求堆中的全部元素都是有序的,而只要求其第一个元素是最大的。这一点与优先队列相似)。l(用push_heap()/pop_heap())添加/删除元素高效:最坏性能为O(logN)。l(用heap_sort())排序也高效:最坏性能为O(N*logN)。STL提供了4种堆的操作(参见表21-14),各有两个重载函数,也是第二个重载多输入参数Comparecomp。下面是部分函数原型(其余类似):表21-13堆算法(4个)功能算法说明压堆push_heap()向堆中加入元素。出堆pop_heap()从堆中弹出元素。造堆make_heap()从序列构造堆。排序堆sort_heap()给堆排序。templatevoidpush_heap(RandomAccessIteratorfirst,RandomAccessIteratorlast);templatevoidpop_heap(RandomAccessIteratorfirst,RandomAccessIteratorlast,Comparecomp); 堆算法的风格相当独特,实现堆操作功能的另一种自然方式,是提供一个带有这4种操作的适配器。结果将会产生类似于优先队列的东西,其实,priority_queue几乎肯定是采用堆来实现的。由push_heap(first,last)压入的值是*(last-1)。这里假设[first,last-1[已经是堆,压入操作将下一个元素加进来,使堆序列扩展为[first,last[。因此,你可以从一个现存的序列出发,通过一系列的push_heap()操作,构造起一个堆来。pop_heap()的功能是去掉堆的第一个元素(最大的元素),它采用的方法是,先将该元素与堆的最后一个元素*(last-1)进行交换,然后再将[first,last-1[做成堆(注意,此时的堆长已被减一,不再包含元素*(last-1)了)。make_heap()用于将序列容器一次性做成堆。sort_heap()则用于将堆中的所有元素全部排好序(降序:头大尾小)。下面是一个使用堆算法的优先队列的例子(先定义优先队列向量类PQV,再用100个随机数构造堆,最后逐个弹出堆首元素(最大值),输出结果见图21-8)://PriorityQueue.cpp#include#include#include#include#include#includeusingnamespacestd;templateclassPQV:publicvector{//优先队列向量模板类Comparecomp;//定义比较对象public:PQV(Comparecmp=Compare()):comp(cmp){//构造函数make_heap(this->begin(),this->end(),comp);//生成堆}constT&top(){returnthis->front();}//弹出首元(最大元素)voidpush(constT&x){//压入 this->push_back(x);//从尾部加入元素push_heap(this->begin(),this->end(),comp);//压堆}voidpop(){//弹出pop_heap(this->begin(),this->end(),comp);//出堆this->pop_back();//从尾部删除(出堆后交换到尾部的元素)}};intmain(){PQV>pqi;//定义优先队列向量类的对象srand(time(0));//初始化随机种子//生成0~24的随机优先队列向量for(inti=0;i<100;i++)pqi.push(rand()%25);cout<<"随机优先队列向量:"<(cout,""));cout<的迭代器类型为::iterator(是一种随机访问迭代器)、list的迭代器类型为list::iterator(是一种双向迭代器)。21.4.1特征与操作迭代器具有解除、赋值、比较、遍历等特征和读、写、访问、迭代、比较等操作。1.特征迭代器的基本特征有:n解除——支持解除引用(dereference)操作,以便可以访问它引用的值。即,如果p是一个迭代器,则应该对*p和p->进行定义(似指针);n赋值——可将一个迭代器赋给另一个迭代器。即,如果p和q都是迭代器,则应该对表达式p=q进行定义;n比较—— 可将一个迭代器与另一个迭代器进行比较。即,如果p和q都是迭代器,则应该对表达式p==q和p!=q进行定义;n遍历——可以使用迭代器来遍历容器中的元素,这可以通过为迭代器p定义++p和p++操作来实现。2.操作迭代器的操作有:n读——通过解除引用*来间接引用容器中的元素值,例如x=*p;n写——通过解除引用*来给容器中的元素赋值,例如*p=x;n访问——通过下标和指向引用容器中的元素及其成员,例如p[2]和p->mn迭代——利用增量和减量运算(++和--、+和-、+=和-=)在容器中遍历、漫游和跳跃,例如p++、--p、p+5、p-=8n比较——利用比较运算符(==、!=、<、>、<=、>=)来比较两个迭代器是否相等或谁大谁小,例如if(p××××√<=和>=××××√21.4.3声明迭代器类iterator和函数的声明都位于命名空间std中,可以在头文件中找到:namespacestd{//取自C++2003标准(节选)//primitives:基础/原语templatestructiterator_traits;//迭代器特征templatestructiterator_traits;//指针的专门化templatestructiterator;//迭代器structinput_iterator_tag{};//迭代器标志(类别)structoutput_iterator_tag{};structforward_iterator_tag:publicinput_iterator_tag{}; structbidirectional_iterator_tag:publicforward_iterator_tag{};structrandom_access_iterator_tag:publicbidirectional_iterator_tag{};//iteratoroperations:迭代器操作templatevoidadvance(InputIterator&i,Distancen);templatetypenameiterator_traits::difference_typedistance(InputIteratorfirst,InputIteratorlast);//predefinediterators:预定义迭代器(及其比较运算符重载)templateclassreverse_iterator;//反向迭代器templatebooloperator==(constreverse_iterator&x,constreverse_iterator&y);……templatereverse_iteratoroperator+(typenamereverse_iterator::difference_typen,constreverse_iterator&x);//插入器templateclassback_insert_iterator;templateback_insert_iteratorback_inserter(Container&x);templateclassfront_insert_iterator;templatefront_insert_iteratorfront_inserter(Container&x);templateclassinsert_iterator;templateinsert_iteratorinserter(Container&x,Iteratori);//streamiterators:流迭代器template,classDistance=ptrdiff_t>classistream_iterator;template>classostream_iterator;template>classistreambuf_iterator;templatebooloperator==(constistreambuf_iterator&a,constistreambuf_iterator&b);templatebooloperator!=(constistreambuf_iterator&a,constistreambuf_iterator&b);template>classostreambuf_iterator;}21.4.4预定义迭代器STL有一个使用方便的预定义迭代器集合,其中包括正向迭代器、反向迭代器、插入器和流迭代器。1.正向迭代器:templatestructiterator;在所有的标准容器类中,都定义了返回iterator对象的成员函数begin()和end()。例如namespacestd{template>classvector{public:……//iterators:迭代器iteratorbegin();//指向首元素const_iteratorbegin()const;iteratorend();//指向尾元素后的一个位置const_iteratorend()const;reverse_iteratorrbegin();//指向反向序列的首元素const_reverse_iteratorrbegin()const;reverse_iteratorrend();//指向反向序列尾元素后的一个位置const_reverse_iteratorrend()const;……}}通过在程序中调用它们,就可以得到正向迭代器iterator 的对象,从而能够正向遍历容器。例如:(c为任意标准容器对象,op为某一函数对象)for_each(c.begin(),c.end(),op);2.反向迭代器:templateclassreverse_iterator;在标准容器中调用rbegin()和rend(),就可以得到反向迭代器reverse_iterator的对象,从而可反向遍历容器。例如(逆序输出代码文件行,结果见图21-11)://Revers.cpp#include#include#include#includeusingnamespacestd;intmain(){ifstreamin("Revers.cpp");if(!in){cout<<"OpenfileRevers.cpperror!"<lines;while(getline(in,line))lines.push_back(line);for(vector::reverse_iteratorr=lines.rbegin();r!=lines.rend();r++)cout<<*r<classback_insert_iterator;//在尾部插入templateback_insert_iteratorback_inserter(Container&x);templateclassfront_insert_iterator;//在头部插入templatefront_insert_iteratorfront_inserter(Container&x);templateclassinsert_iterator;//在中间插入templateinsert_iteratorinserter(Container&x,Iteratori);例如(输出结果见图21-12):图21-12插入器例的输出//Insert.cpp#include#include#include#include#includeusingnamespacestd;inta[]={1,3,5,7,11,13,17,19,23};//质数序列templatevoidfrontInsertion(Cont&ci){//前插 copy(a,a+sizeof(a)/sizeof(Cont::value_type),front_inserter(ci));//插入acopy(ci.begin(),ci.end(),ostream_iterator(cout,""));//插入空格cout<voidbackInsertion(Cont&ci){//后插copy(a,a+sizeof(a)/sizeof(Cont::value_type),back_inserter(ci));//插入acopy(ci.begin(),ci.end(),ostream_iterator(cout,""));//插入空格cout<voidmidInsertion(Cont&ci){//中插typenameCont::iteratorit=ci.begin();++it;++it;++it;//迭代器指向第4个元素copy(a,a+sizeof(a)/(sizeof(Cont::value_type)*2),inserter(ci,it));//插入9/2=4个数copy(ci.begin(),ci.end(),ostream_iterator(cout,""));//插入空格cout<di;listli;vectorvi;frontInsertion(di);frontInsertion(li);//frontInsertion(vi);//错误!对向量不能使用前插di.clear();li.clear();backInsertion(vi);backInsertion(di);backInsertion(li);midInsertion(vi);midInsertion(di);midInsertion(li); }4.流迭代器一般I/O(Input/Output,输入/输出)是由C++流库或C的I/O函数完成,也可以通过GUI的对话框等来进行I/O操作。这些I/O接口的基本目标,是读取各种类型的单个值。为了使I/O能够以序列的方式呈现,将流I/O融入容器和算法的通用框架之中,STL还提供了4个流迭代器的模版类:listream_iterator(输入流迭代器)——用于从输入流读取。lostream_iterator(输出流迭代器)——用于向输出流写入。listreambuf_iterator(输入流缓冲区迭代器)——用于从输入流缓冲区读取。lostreambuf_iterator(输出流缓冲区迭代器)——用于向输出流缓冲区写入。从输入流读取的操作,由对输入流迭代器is的间接引用*is的赋值来进行,在每两次输入之间,必须进行一次增量操作,为下一次输入做好准备。类似地,写出到输出流的操作,由对输出流迭代器os的间接引用*os的赋值来进行,在每两次输出之间,也必须进行一次增量操作,为下一次输出做好准备。例如(输出结果见图21-13)://Stream.cpp#include图21-13流迭代器例的输出#includeusingnamespacestd;intmain(){ostream_iteratoros(cout);//将int通过os输出到cout*os=5;//输出5(用cout<<5;)os++;//准备好下一次的输出*os=80;istream_iteratoris(cin);//通过is从cin读入intinti1=*is;//输入到i1is++;//准备好下一次的输入inti2=*is;//输入到i2 cout<<"i1="<#include#include#include#includeusingnamespacestd;intmain(){ifstreamin("StreamIt.cpp");//创建输入文件流对象istream_iteratorbegin(in),end;//定义串输入流迭代器ostream_iteratorout(cout," ");//定义串输出流迭代器vectorvs;//定义串向量copy(begin,end,back_inserter(vs));//复制代码文件后插到向量copy(vs.begin(),vs.end(),out);//复制向量到输出迭代器*out++=vs[0];//输出向量的第一个单词*out++="That'sall,folks!";//输出该字符串}21.4.5指针与迭代器既然迭代器是广义的指针,那么指针本身是不是迭代器呢?其实,指针满足所有迭代器的要求,所以,指针就是一种迭代器。迭代器是泛型算法的接口,而指针是迭代器。所以,各种STL算法,也可以使用指针来对非标准容器(如数组)进行操作。即,利用指针做迭代器,可以将STL算法用于常规数组。例如排序函数sort:sort(Ranfirst,Ranlast);//Ran表示随机访问迭代器 对容器c为:sort(c.begin(),c.end());对数组a可以改为(constintSIZE=100;inta[SIZE];):sort(a,a+SIZE);又例如复制函数copy(In和Out分别表示输入和输出迭代器):copy(Infirst,Inlast,Outres);对容器c可为(ostream_iteratorout_iter(cout);):copy(c.begin(),c.end(),out_iter);对数组a可以改为:copy(a,a+SIZE,out_iter);或(复制数组a中的元素到容器c):copy(a,a+SIZE,c.begin());21.5分配器分配器(allocator)用于将算法和容器的实现(它们都需要分配存储),使程序员被隔离于物理存储的细节之外。它提供了一种映射,将具有数组和字节形式的低级数据模型映射到高级的对象模型。最常见的低级存储模型本身是由几个标准函数支持的。分配器提供了一套分配与释放存储的标准方式,以及一套用作指针类型和引用类型的标准名称。与迭代器一样,分配器也是一种纯粹的抽象,任何行为上像分配器的类型都是分配器。但是,图21-13利用流迭代器输出代码文件单词与每个程序员都需要熟悉的迭代器不同,分配器仅仅是一种支持机制,程序员很少需要考虑它,一般也不需要程序员自己去写新的分配器。21.5.1标准分配器C++的标准库,提供了一个标准分配器,目的是为大多数用户提供足够好的服务。allocator模版类被定义在头文件中。namespacestd{//取自C++2003标准templateclassallocator; //specializeforvoid:template<>classallocator{public:typedefvoid*pointer;typedefconstvoid*const_pointer;//reference-to-voidmembersareimpossible.没有引用typedefvoidvalue_type;templatestructrebind{typedefallocatorother;};};templateclassallocator{public:typedefsize_tsize_type;typedefptrdiff_tdifference_type;typedefT*pointer;typedefconstT*const_pointer;typedefT&reference;typedefconstT&const_reference;typedefTvalue_type;templatestructrebind{typedefallocatorother;};allocator()throw();allocator(constallocator&)throw();templateallocator(constallocator&)throw();˜allocator()throw();pointeraddress(referencex)const;const_pointeraddress(const_referencex)const;pointerallocate(size_type,allocator::const_pointerhint=0);voiddeallocate(pointerp,size_typen);size_typemax_size()constthrow();voidconstruct(pointerp,constT&val); voiddestroy(pointerp);};}例如,操作(allocatoralloc;)typenameallocator::pointerp=alloc.allocator(n);为指针(迭代器)p分配n个对象所用的空间。而操作deallocate(p,n);则释放原来所分配的空间。除了使用标准分配器外,你也可以自己定义分配器。例如,使用共享存储、带垃圾收集的存储、来自预分配对象池的存储等的分配器。21.5.2未初始化的存储在头文件中,还为处理未初始化的存储提供了若干标准函数。l复制未初始化存储块的3个标准函数:templateForwardIteratoruninitialized_copy(InputIteratorfirst,InputIteratorlast,ForwardIteratorresult);templatevoiduninitialized_fill(ForwardIteratorfirst,ForwardIteratorlast,constT&x);templatevoiduninitialized_fill_n(ForwardIteratorfirst,Sizen,constT&x);这些函数本质上是想提供给容器和算法的实现者(不是使用者)。算法为了达到可接受的性能,常常需要一些临时空间。这种临时空间最好通过一个标准操作来分配,但是直到实际需要用其中的位置时,再来进行初始化。l分配和释放未初始化空间的2个标准函数:templatepairget_temporary_buffer(ptrdiff_tn);//分配,但不初始化templatevoidreturn_temporary_buffer(T*p);//释放,但不销毁l原始存储迭代器模版类namespacestd{//取自C++2003标准templateclassraw_storage_iterator:publiciterator {public:explicitraw_storage_iterator(OutputIteratorx);raw_storage_iterator&operator*();raw_storage_iterator&operator=(constT&element);raw_storage_iterator&operator++();raw_storage_iteratoroperator++(int);};}21.6字符串类为了展示STL的强大功能,本节介绍标准C++新增加的字符串类string(串)和wstring(宽串),它们都是模版字符串类basic_string(基本串)的实例类。basic_string是STL中的串拟容器,提供下标操作、随机访问迭代器和其他序列容器的几乎所有功能,但是它只支持有限的元素类型(char和wchar_t),而且它还为作为字符串使用进行了优化。类string和wstring分别是模版字符串类basic_string:template,classAllocator=allocator>classbasic_string;对char和wchar_t这两种字符类型的实例化(参见图21-14):typedefbasic_stringstring;typedefbasic_stringwstring;实例化wchar_tcharbasic_stringstringwstring模版类实例类图21-14标准C++串类的层次结构这三个串类都被定义在头文件中,对应的(英文)帮助信息位于MSDN的“目录开发工具和语言VisualStudio文档VisualC++参考信息LibrariesReferenceStandardC++LibraryStandardC++LibraryHeaderFiles”中。 21.6.1头文件在该头文件中,定义了基本串模版类basic_string,重载了拼接运算符+,比较运算符==、!=、<、>、<=和>=,流I/O运算符<<和>>;还定义了交换函数swap和获取文本行函数getline;最后定义了实例化的普通字符串类string和宽字符串类wstring。下面是头文件的具体内容:namespacestd{//取自C++2003标准(节选)//charactertraits:字符特性templatestructchar_traits;template<>structchar_traits;template<>structchar_traits;//basic_string:基本串template,classAllocator=allocator>classbasic_string;//运算符重载templatebasic_stringoperator+(constbasic_string&lhs,constbasic_string&rhs);……templatevoidswap(basic_string&lhs,basic_string&rhs);//交换templatebasic_istream&operator>>(basic_istream&is,basic_string&str);templatebasic_ostream&operator<<(basic_ostream&os,constbasic_string&str);templatebasic_istream&getline(basic_istream&is,basic_string&str,charTdelim);//获取文本行 typedefbasic_stringstring;typedefbasic_stringwstring;}可见basic_string、string和wstring类都被定义在命名空间std中,所以必须使用std::前缀,或者先写上语句usingnamespacestd;后才能使用这些串类。21.6.2类的定义与构造下面讨论基本串模版类basic_string及其实例化具体类string和wstring的定义、构造函数及其用法。1.定义template,//值类型的各种重要属性classAllocator=allocator//分配器,缺省为allocator>classbasic_string;typedefbasic_stringstring;typedefbasic_stringwstring;2.构造函数l基本串模版类basic_string:explicitbasic_string(constallocator_type&_Al=Allocator());basic_string(constvalue_type*_Ptr,constallocator_type&_Al=Allocator());basic_string(size_type_Count,value_type_Ch,constallocator_type&_Al=Allocator());basic_string(constvalue_type*_Ptr,size_type_Count,constallocator_type&_Al=Allocator());basic_string(constbasic_string&_Right,size_type_Roff=0,size_type_Count=npos,constallocator_type&_Al=Allocator());l实例化具体类string(分配器采用缺省值): explicitstring();//构造空串对象string(constchar*ptr);//构造含指定串的对象string(constchar*ptr,size_tn);//构造含指定串前n个字符的串对象string(size_tn,charch);//构造含n个指定字符的串对象//构造含指定串的从第roff个字符开始的n个字符的串对象//无n时到串尾,无roff时从串头开始string(conststring&right,size_troff=0,size_tn=npos);l实例化具体类wstring(分配器也采用缺省值):explicitwstring();//构造空串对象wstring(constwchar_t*ptr);//构造含指定串的对象wstring(constwchar_t*ptr,size_tn);//构造含指定串前n个字符的串对象wstring(size_tn,wchar_tch);//构造含n个指定字符的串对象//构造含指定串的从第roff个字符开始的n个字符的串对象//无n时到串尾,无roff时从串头开始wstring(constwstring&right,size_troff=0,size_tn=npos);其中:staticconstsize_typenpos=-1;表示"notfound"或"allremainingcharacters"。3.例子下面是构造字符串对象的若干实例语句:usingnamespacestd;charstr[]="Hello";strings0;//空串strings1(str);//含"Hello"strings2(str,2);//含"He"strings3(5,'a');//含"aaaaa"strings4(s1,1,3);//含"ell"strings5(s1);//含"Hello"(从头到尾)cout<>从输入流提取串operator!=不等operator==相等operator<小于operator>大于operator<=小于等于operator>=大于等于为了简短实用,下面主要以wstring为例(其余类的同名函数在形式上是完全类似的),按字母顺序介绍串类的若干常用的成员函数和重载运算符。2.添加(append)append函数将指定内容添加到当前串的末尾,它有6个重载:wstring&append(constwchar_t*ptr);//添加整个宽字符数组串wstring&append(constwchar_t*ptr,size_tn);//添加串,至多n个宽字符wstring&append(size_tn,wchar_tch);//添加n个同样的宽字符chwstring&append(constwstring&str);//添加整个宽字符串//添加从串str的off处开始的至多n个宽字符wstring&append(constwstring&str,size_toff,size_tn);//添加从输入迭代器first和last指定范围内的所有宽字符templatewstring&append(InputIteratorfirst,InputIteratorlast);其实,串类也重载了赋值运算符+=: basic_string&operator+=(value_type_Ch);//添加单个字符basic_string&operator+=(constvalue_type*_Ptr);//添加字符数组basic_string&operator+=(constbasic_string&_Right);//添加字符串因此,也可以用+=赋值来代替部分append函数的功能,例如:wstrings1(L"Hello");//含"Hello"s1+=L'';//含"Hello"s1+=L"World";//含"HelloWorld"wstrings2(L"!");//含"!"s1+=s2;//含"HelloWorld!"_putws(s1.data());//输出字符串到屏幕(不能用cout<wstring&assign(InputIteratorfirst,InputIteratorlast);其实,串类也重载了赋值运算符=:basic_string&operator=(value_type_Ch);basic_string&operator=(constvalue_type*_Ptr);basic_string&operator=(constbasic_string&_Right);因此,也可以用=赋值来代替部分assign函数的功能,例如:wstrings0=L'H';//错误!不能用单个字符对串对象进行初始化(因为初始化必须//调用构造函数,而串类没有以单个字符为唯一输入参数的构造函数)wstrings0;//空串s0=L'H';//正确,含"H"wstrings1=L"Hello";//含"Hello" wstrings2=s1;//也含"Hello"_putws(s2.c_str());//输出字符串到屏幕4.定位(at)at函数获取串中指定位置的字符(引用):const_referenceat(charoff)const;//常型referenceat(charoff);//可修改虽然,串类也重载了[]运算符:const_referenceoperator[](size_type_Off)const;referenceoperator[](size_type_Off);从而可直接用下标访问串中的字符,但是这种访问没有越界检查,不安全。而at函数则是带边界检查的安全访问,如果访问超出字符串范围(越界),at就会抛出out_of_range异常。其中,引用类型reference的定义为:typedeftypenameallocator_type::referencereference;typedefAllocatorallocator_type;classAllocator=allocator;//allocator或allocator可以用于访问和修改串中字符元素的值。对wstring类,reference等价于wchar_t&。而常型引用类型const_reference,只能用于访问串中字符元素,但是不能修改其值(等价于constchar&):typedeftypenameallocator_type::const_referenceconst_reference;例如:usingnamespacestd;stringstr1("Helloworld"),str2("Goodbyeworld");cout<<"Theoriginalstringstr1is:"<::referencerefStr1=str1[6];basic_string::referencerefStr2=str2.at(3);cout<<"Thecharacterwithanindexof6instringstr1is:"<Stack;Stacktextlines;ifstreamin("Stack.cpp");stringline;while(getline(in,line))textlines.push(line+" ");8.运算符除了串内重载了三个运算符(=、+=、[])外,在头文件中,还重载了拼接运算符+,比较运算符==、!=、<、>、<=和>=,流I/O运算符<<和>>。例如:strings1,s2,s3;……s3=s1+s2;if(s1==s2)……;由于篇幅的限制,其他成员函数和重载运算符就不再详细介绍了,其中的复制、查找、替换等操作的功能与STL中的算法类似。复习思考题1.给出泛型的英文单词和另外的中文译文。2.面向过程、面向对象和泛型编程各自使用的是什么软件重用方式? 1.给出STL的英文原文和中文译文。2.给出STL的设计目标和主要内容组件。3.对泛型编程和STL贡献最大的是哪两个人?4.STL是哪一年被ANSI/ISOC++标准委员会接受的?5.容器和迭代器可以分别视为是什么的扩展和抽象?6.STL中的容器被分成哪些类型?7.容器适配器和拟容器与真正的独立容器有什么差别?8.什么容器中的元素必须是有序的?9.哪些容器不能随机访问?10.哪些容器只能在尾部添加元素?11.哪些容器可以在任意位置添加元素?12.映射属于什么容器类型?它的元素是什么值类型?13.STL中的算法和函数对象是什么关系?14.C++标准库中提供了多少种算法函数?可以分成哪三大类?15.不用参数就可以调用的函数对象叫什么?16.返回bool值的一元函数对象叫什么?17.函数适配器是干什么用的?有哪些种类?18.非修改性序列算法有哪些?19.查找和搜索有什么区别?20.修改性序列算法有哪些?21.STL提供了那些排序算法?22.STL中定义了哪5种迭代器?它们之间可形成什么结构?23.迭代器有哪些操作?24.什么操作被所有种类的迭代器支持?什么迭代器支持所有的操作?25.STL中有哪些预定义迭代器?26.迭代器是广义的指针。指针本身是迭代器吗?27.分配器有是用处?一般用户需要自己编写分配器吗?28.标准C++中定义了哪三种字符串类,它们之间是什么关系?它们与STL有关吗?29.string和wstring的区别在哪里?30.这些字符串类增加了哪些实用的运算符? 1.如何获取串长?2.如何获取串对象中的字符数组?3.下标索引[]和定位函数at有什么差别?练习题1.(选做题,例子)实现并运行本章中的各例。2.(计数排序)改写单词计数的map应用程序WordCount.cpp,实现按计数的大小顺序输出单词与计数。(提示:可以另外创建一个值类型也是pair的序列容器,并将map容器的内容assign赋值给它,然后再对它进行排序后输出。)3.(选做题,字符转换)编写宽字符串(UTF-16)与普通字符串(GB2312)的相互转换函数。(提示:可以利用MFC的CString的AllocSysString()和STL的map容器)

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭