STL进修系列之一:C++ STL轻松导学
副标题#e#
目次1 初识STL:解答一些疑问
1.1 一个最体贴的问题:什么是STL
1.2 追根溯源:STL的汗青
1.3 千丝万缕的接洽
1.3.1 STL和C++
1.3.2 STL和C++尺度库
1.3.3 STL和GP,GP和OOP
1.4 STL的差异实现版本
1.4.1 HP STL
1.4.2 P.J. Plauger STL
1.4.3 Rouge Wave STL
1.4.4 STLport
1.4.5 SGI STL
2 牛刀小试:且看一个简朴例程
2.1 引子
2.2 例程实作
2.2.1 第一版:史前时代–转木取火
2.2.2 第二版:家产时代–组件化大出产
2.2.3 第三版:唯美主义的精品
2.3 汗青的评价
2.4 如何运行
#p#副标题#e#
作为C++尺度不行缺少的一部门,STL应该是渗透在C++措施的角角落落里的。STL不是尝试室里的宠儿,也不是措施员桌上的放置,她的冲感人心并非好景不常。本教程旨在流传和普及STL的基本常识,若能借此时机为STL的推广做些力所能及的工作,到也是件让人愉快的工作。
1 初识STL:解答一些疑问1.1 一个最体贴的问题:什么是STL1.2 追根溯源:STL的汗青1.3 千丝万缕的接洽1.3.1 STL和C++1.3.2 STL和C++尺度函数库1.3.3 STL和GP,GP和OOP1.4 STL的差异实现版本1.4.1 HP STL1.4.2 P.J. Plauger STLhttp://www.dinkumware.com。据称Visual Studio.NET中的Visual C++.NET(即VC7.0),对C++尺度的支持有所提高,而且多了以哈希表(hash table)为基本而实现的map容器,multimap容器和set容器。
"什么是STL?",如果你对STL还知之甚少,那么我想,你必然很想知道这个问题的谜底,坦率地讲,要指望用短短数言将这个问题叙述清楚,也决非易事。因此,假如你在看完本节之后照旧以为似懂非懂,大可不必着急,在阅读了后续内容之后,相信你对STL的认识,将会愈加清晰、精确和完整。不外,上述这番话听起来是否有点像是在为本身糟糕的表达本领开脱罪责呢?:)
不知道你是否有过这样的经验。在你筹备着手完成数据布局老师所部署的家庭功课时,可能在你为你所认真的某个软件项目中添加一项新成果时,你发明需要用到一个链表(List)可能是映射表(Map)之类的对象,可是手头并没有现成的代码。于是在你开始正式思量措施成果之前,手工实现List可能Map是不行制止的。于是……,最终你顺利完成了任务。或者此时,作为一个具有较高素养的措施员的你还不愿罢休(可能是一个喜欢偷懒的优等生:),因为你会想到,假如今后还碰着这样的环境怎么办?没有须要再做一遍同样的工作吧!
假如说上述这种景象天天都在产生,或者有点浮夸。可是,假如说整个软件规模里,数十年来确实都在为了一个方针而格斗–可复用性(reusability),这看起来好像并不浮夸。从最早的面向进程的函数库,到面向工具的措施设计思想,到各类组件技能(如:COM、EJB),到设计模式(design pattern)等等。而STL也在做着雷同的工作,同时在它背后蕴涵着一种新的措施设计思想–泛型化设计(generic programming)。
继承上面提到的谁人例子,如果你把List可能map完好的保存了下来,正在暗自自得。且慢,假如下一回的List里放的不是浮点数而是整数呢?假如你所实现的Map在效率上老是令你不太满足而且有时还会出些bug呢?你该如何面临这些问题?利用STL是一个不错的选择,确实如此,STL可以大度地办理上面提到的这些问题,尽量你还可以寻求其他要领。
说了半天,到底STL是什么对象呢?
STL(Standard Template Library),即尺度模板库,是一个具有家产强度的,高效的C++措施库。它被容纳于C++尺度措施库(C++ Standard Library)中,是ANSI/ISO C++尺度中最新的也是极具革命性的一部门。该库包括了诸多在计较机科学规模里所常用的根基数据布局和根基算法。为宽大C++措施员们提供了一个可扩展的应用框架,高度浮现了软件的可复用性。这种现象有些雷同于Microsoft Visual C++中的MFC(Microsoft Foundation Class Library),可能是Borland C++ Builder中的VCL(Visual Component Library),对付此二者,各人必然不会生疏吧。
从逻辑条理来看,在STL中浮现了泛型化措施设计的思想(generic programming),引入了诸多新的名词,好比像需求(requirements),观念(concept),模子(model),容器(container),算法(algorithmn),迭代子(iterator)等。与OOP(object-oriented programming)中的多态(polymorphism)一样,泛型也是一种软件的复用技能。
从实现条理看,整个STL是以一种范例参数化(type parameterized)的方法实现的,这种方法基于一个在早先C++尺度中没有呈现的语言特性–模板(template)。假如查阅任何一个版本的STL源代码,你就会发明,模板作为组成整个STL的基石是一件千真万确的工作。除此之外,尚有很多C++的新特性为STL的实现提供了利便。
#p#分页标题#e#
不知你对这里一下子冒出这么多术语做何感触,但愿不会另你不愉快。如果你对它们之中的大大都不甚相识,敬请安心,在后续内容中将会对这些名词逐一阐述。正如开头所提到的。
有趣的是,对付STL尚有别的一种表明–STepanov & Lee,前者是指Alexander Stepanov,STL的首创人;尔后者是Meng Lee,她也是使STL得以奉行的元勋,第一个STL制品就是他们相助完成的。这一提法源自1995年3月,Dr.Dobb’s Journal特约记者, 著名技能书籍作家Al Stevens对Alexander Stepanov的一篇专访。
在结识新伴侣的时候,大大都人老是忍不住想相识对方的已往。本节将带您简朴回首一下STL的已往。
被誉为STL之父的Alexander Stepanov,出生于苏联莫斯科,早在20世纪70年月后半期,他便已经开始思量,在担保效率的前提下,将算法从诸多详细应用之中抽象出来的大概性,这即是厥后泛型化思想的雏形。为了验证本身的思想,他和纽约州立大学传授Deepak Kapur,伦塞里尔技能学院传授David Musser配合开拓了一种叫做Tecton的语言。尽量这次实验最终没有取得实用性的成就,但却给了Stepanov很大的启示。
在随后的几年中,他又和David Musser等人先后用Schema语言(一种Lisp语言的变种)和Ada语言成立了一些大型措施库。这其间,Alexander Stepanov开始意识到,在其时的面向工具措施设计思想中所存在的一些问题,好比抽象数据范例观念所存在的缺陷。Stepanov但愿通过对软件规模中各构成部门的分类,逐渐形成一种软件设计的观念性框架。
1987年阁下,在贝尔尝试室事情的Alexander Stepanov开始首次回收C++语言举办泛型软件库的研究。但遗憾的是,其时的C++语言还没有引入模板(template)的语法,此刻我们可以清楚的看到,模板观念之于STL实现,是多么重要。是时使然,回收担任机制是别无选择的。尽量如此,Stepanov照旧开拓出了一个复杂的算法库。与此同时,在与Andrew Koenig(前ISO C++尺度化委员会主席)和Bjarne Stroustrup(C++语言的首创人)等顶级大家们的共事进程中,Stepanov开始留意到C/C++语言在实现其泛型思想方面所具有的潜在优势。就拿C/C++中的指针而言,它的机动与高效运用,使厥后的STL在实现泛型化的同时更是保持了高效率。别的,在STL中占据极其重要职位的迭代子观念即是源自于C/C++华夏生指针( native pointer)的抽象。
1988年,Alexander Stepanov开始进入惠普的Palo Alto尝试室事情,在随后的4年中,他从事的是有关磁盘驱动器方面的事情。直到1992年,由于介入并主持了尝试室主任Bill Worley所成立的一个有关算法的研究项目,才使他从头回到了泛型化算法的研究事情上来。项目自成立之后,参加者从最初的8人逐渐淘汰,最后只剩下两小我私家–Stepanove本人和Meng Lee。颠末长时间的尽力,最终,信念与汗水所换来的是一个包括有大量数据布局和算法部件的复杂运行库。这即是此刻的STL的雏形(同时也是STL的一个实现版本–HP STL)。
1993年,其时在贝尔尝试室的Andrew Koenig看到了Stepanove的研究成就,极端欢快。在他的勉励与辅佐下,Stepanove于是年9月的圣何塞为ANSI/ISO C++尺度委员会做了一个相关演讲(题为"The Science of C++ Programming"),向委员们报告了其见识。然后又于次年3月,在圣迭戈集会会议上,向委员会提交了一份发起书,以期使STL成为C++尺度库的一部门。尽量这一发起十分复杂,以至于低落了被通过的大概性,但由于其所包括的新思想,投票功效以压倒大都的意见认为推迟对该发起的抉择。
随后,在众人的辅佐之下,包罗Bjarne Stroustrup在内,Stepanove又对STL举办了改造。同时插手了一个封装内存模式信息的抽象模块,也就是此刻STL中的allocator,它使STL的大部门实现都可以独立于详细的内存模式,从而独立于详细平台。在同年夏季的滑铁卢集会会议上,委员们以80%赞成,20%阻挡,最终通过了提案,抉择将STL正式纳入C++尺度化历程之中,随后STL便被放进了集会会议的事情文件中。自此,STL终于成为了C++家属中的重要一员。
从此,跟着C++尺度的不绝改造,STL也在不绝地作着相应的演化。直至1998年,ANSI/ISO C++尺度正式定案,STL始终是C++尺度中不行或缺的一大部件。
在你相识了STL的已往之后,一些名词开始不绝在你的大脑中表现,STL、C++、C++尺度函数库、泛型措施设计、面向工具措施设计……,这些观念意味着什么?他们之间的干系又是什么?假如你想相识某些细节,这里也许有你但愿获得的谜底。
#p#分页标题#e#
没有C++语言就没有STL,这么说绝不为过。一般而言,STL作为一个泛型化的数据布局和算法库,并不牵涉详细语言(虽然,在C++里,它被称为STL)。也就是说,假如条件答允,用其他语言也可以实现之。这里所说的条件,主要是指雷同于"模板"这样的语法机制。假如你没有略过前一节内容的话,应该可以看到,Alexander Stepanov在选择C++语言作为实现东西之前,早以回收过多种措施设计语言。可是,为什么最终照旧C++幸运的包袱了这个汗青性任务呢?原因不只在于前述谁人条件,还在于C++在某些方面所表示出来的优越特性,好比:高效而机动的指针。可是假如把C++作为一种OOP(Object-Oriented Programming,面向工具措施设计)语言来对待的话(事实上我们一般都是这么认为的,不是吗?),其成果强大的担任机制却没有给STL的实现帮上多大的忙。在STL的源代码里,并没有太多太巨大的担任干系。担任的思想,甚而面向工具的思想,还不敷以实现雷同STL这样的泛型库。C++只有在引入了"模板"之后,才直接导致了STL的降生。这也正是为什么,用其他比C++更纯的面向工具语言无法实现泛型思想的一个重要原因。虽然,工作老是在变革之中,像Java在这方面,就是一个很好的例子,jdk1.4中已经插手了泛型的特性。
另外,STL对付C++的成长,尤其是模板机制,也起到了促进浸染。好比:模板函数的偏特化(template function partial specialization),它被用于在特定应用场所,为一般模板函数提供一系列非凡化版本。这一特性是继STL被ANSI/ISO C++尺度委员会通过之后,在Bjarne和Stepanov配合商讨之下并由Bjarne向委员会提出发起的,最终该项发起被通过。这使得STL中的一些算法在处理惩罚非凡景象时可以选择非一般化的方法,从而担保了执行的效率。
STL是最新的C++尺度函数库中的一个子集,这个复杂的子集占据了整个库的约莫80%的分量。而作为在实现STL进程中饰演要害脚色的模板则充斥了险些整个C++尺度函数库。在这里,我们有须要看一看C++尺度函数库里包括了哪些内容,个中又有哪些是属于尺度模板库(即STL)的。
C++尺度函数库为C++措施员们提供了一个可扩展的基本性框架。我们从中可以得到极大的便利,同时也可以通过担任现有类,本身体例切合接口类型的容器、算法、迭代子等方法对之举办扩展。它大抵包括了如下几个组件:
C尺度函数库,根基保持了与原有C语言措施库的精采兼容,尽量有些微变革。人们总会忍不住迷恋已往的优美岁月,假如你曾经是一个C措施员,对这一点必然体会颇深。或者有一点会让你以为奇怪,那就是在C++尺度库中存在两套C的函数库,一套是带有.h扩展名的(好比<stdio.h>),而另一套则没有(好比<cstdio>)。它们确实没有太大的差异。
语言支持(language support)部门,包括了一些尺度范例的界说以及其他特性的界说,这些内容,被用于尺度库的其他处所或是详细的应用措施中。
诊断(diagnostics)部门,提供了用于措施诊断和报错的成果,包括了异常处理惩罚(exception handling),断言(assertions),错误代码(error number codes)三种方法。
通用东西(general utilities)部门,这部门内容为C++尺度库的其他部门提供支持,虽然你也可以在本身的措施中挪用相应成果。好比:动态内存打点东西,日期/时间处理惩罚东西。记着,这里的内容也已经被泛化了(即回收了模板机制)。
字符串(string)部门,用来代表和处理惩罚文本。它提供了足够富厚的成果。事实上,文本是一个string工具,它可以被看作是一个字符序列,字符范例大概是char,可能wchar_t等等。string可以被转换成char*范例,这样便可以和以前所写的C/C++代码僻静共处了。因为当时侯除了char*,没有此外。
国际化(internationalization)部门,作为OOP特性之一的封装机制在这里饰演着消除文化和地区差此外脚色,回收locale和facet可觉得措施提供浩瀚国际化支持,包罗对各类字符集的支持,日期和时间的暗示,数值和钱币的处理惩罚等等。究竟,在中国和在美国,人们暗示日期的习惯是差异的。
容器(containers)部门,STL的一个重要构成部门,涵盖了许大都据布局,好比前面曾经提到的链表,尚有:vector(雷同于巨细可动态增加的数组)、queue(行列)、stack(仓库)……。string也可以看作是一个容器,合用于容器的要领同样也合用于string。此刻你可以轻松的完成数据布局课程的家庭功课了。
#p#分页标题#e#
算法(algorithms)部门,STL的一个重要构成部门,包括了约莫70个通用算法,用于操控各类容器,同时也可以操控内建数组。好比:find用于在容器中查找便是某个特定值的元素,for_each用于将某个函数应用到容器中的各个元素上,sort用于对容器中的元素排序。所有这些操纵都是在担保执行效率的前提下举办的,所以,假如在你利用了这些算法之后措施变得效率底下,首先必然不要猜疑这些算法自己,仔细查抄一下措施的其他处所。
迭代器(iterators)部门,STL的一个重要构成部门,假如没有迭代器的笼络,容器和算法便无法团结的如此完美。事实上,每个容器都有本身的迭代器,只有容器本身才知道如何会见本身的元素。它有点像指针,算法通过迭代器来定位和操控容器中的元素。
数值(numerics)部门,包括了一些数学运算成果,提供了复数运算的支持。
输入/输出(input/output)部门,就是颠末模板化了的原有尺度库中的iostream部门,它提供了对C++措施输入输出的根基支持。在成果上保持了与原有iostream的兼容,而且增加了异常处理惩罚的机制,并支持国际化(internationalization)。
总体上,在C++尺度函数库中,STL主要包括了容器、算法、迭代器。string也可以算做是STL的一部门。
图1:STL和C++尺度函数库
正如前面所提到的,在STL的背后蕴含着泛型化措施设计(GP)的思想,在这种思想里,大部门根基算法被抽象,被泛化,独立于与之对应的数据布局,用于以沟通或临近的方法处理惩罚各类差异景象。这一思想和面向工具的措施设计思想(OOP)不尽沟通,因为,在OOP中更注重的是对数据的抽象,即所谓抽象数据范例(Abstract Data Type),而算法例凡是被隶属于数据范例之中。险些所有的工作都可以被看作类可能工具(即类的实例),凡是,我们所看到的算法被作为成员函数(member function)包括在类(class)中,类和类则组成了错综巨大的担任体系。
尽量在象C++这样的措施设计语言中,你还可以用全局函数来暗示算法,可是在雷同于Java这样的纯面向工具的语言中,全局函数已经被"迫令克制"了。因此,用Java来模仿GP思想是颇为坚苦的。假如你对前述的STL汗青尚有印象的话,应该记得Alexander Stepanove也曾用基于OOP的语言实验过实现GP思想,可是结果并欠好,包罗没有引入模板之前的C++语言。站在巨人的肩膀上,我们可以得出这样的结论,在OOP中所浮现的思想与GP的思想确实是相异的。C++并不是一种纯面向工具的措施设计语言,它的绝妙之处,就在于既满意了OOP,又玉成了GP。对付后者,模板立下了汗马功勋。别的,需要指出的是,尽量GP和OOP有诸多差异,但这种差异还不至于到"水火不容"的境地。而且,在实际运用的时候,两者的团结利用往往可以使问题的办理更为有效。作为GP思想实例的STL自己即是一个很好的典型,假如没有担任,不知道STL会是什么样子,好像没有人做过这样的试验。
相信你对STL的感性认识应该有所提高了,是该做一些实际的事情了,那么我们首先来相识一下STL的差异实现版本。ANSI/ISO C++文件中的STL是一个仅被描写在纸上的尺度,对付诸多C++编译器而言,需要有各自实际的STL,它们或多或少的实现了尺度中所描写的内容,这样才气够为我们所用。之所以有差异的实现版本,则存在诸多原因,有汗青的原因,也有各自编译器出产厂商的原因。以下是几个常见的STL实现版本。
HP STL是所有其它STL实现版本的来源。它是STL之父Alexander Stepanov在惠普的Palo Alto尝试室事情时,和Meng Lee配合完成的,是第一个STL的实现版本(拜见1.2节)。这个STL是开放源码的,所以它答允任何人免费利用、复制、修改、宣布和销售该软件和相关文档,前提是必需在所有相关文件中插手HP STL的版本信息和授权信息。此刻已经很少直接利用这个版本的STL了。
P. J. Plauger STL属于小我私家作品,由P. J. Plauger本人实现,是HP STL的一个担任版本,因此在其所有头文件中都含有HP STL的相关声明,同时尚有P. J. Plauger本人的版权声明。P. J. Plauger是尺度C中stdio库的早期实现者,此刻是C/C++ User’s Journal的主编,与Microsoft保持着精采的干系。P. J. Plauger STL即是被用于Microsoft的Visual C++中的。在Windows平台下的同类版本中,其机能不错,可是queue组件(行列,一种容器)的效率不抱负,同时由于Visual C++对C++语言尺度的支持不是很好(至少直到VC6.0为止,照旧如此),因此必然水平上影响了P. J. Plauger STL的机能。另外,该版本的源代码可读性较差,你可以在VC的Include子目次下找到所有源文件(好比:C:\Program Files\Microsoft Visual Studio\VC98\Include)。因为不是开放源码的(open source),所以这些源代码是不能修改和销售的,今朝P.J. Plauger STL由Dinkumware公司提供相关处事,详情请见
#p#分页标题#e#
1.4.3 Rouge Wave STLhttp://www.rougewave.com。遗憾的是该版本已有一段时间没有更新且不完全切合尺度。因此在Borland C++ Builder 6.0中,它的职位被另一个STL的实现版本–STLport(见后)代替了。可是思量到与以前版本的兼容,C++ Builder 6.0照旧保存了Rouge Wave STL,只是假如你想查察它的源代码的话,需要在此外目次中才气找到(好比:C:\Program Files\Borland\Cbuilder6\Include\oldstl)。
Rouge Wave STL是由Rouge Wave公司实现的,也是HP STL的一个担任版本,除了HP STL的相关声明之外,尚有Rouge Wave公司的版权声明。同时,它也不是开放源码的,因此无法修改和销售。该版本被Borland C++ Builder所回收,你可以在C++ Builder的Include子目次下找到所有头文件(好比:C:\Program Files\Borland\Cbuilder5\Include)。尽量Rouge Wave STL的机能不是很好,但由于C++ Builder对C++语言尺度的支持还算不错,使其表示在必然水平上得以改进。另外,其源代码的可读性较好。可以从如下网站获得更具体的环境先容:
1.4.4 STLporthttp://www.stlport.org,可以免费下载其源代码。STLport已经被C/C++技能委员会接管成为家产尺度,且在很多平台上都支持。按照测试STLport的效率比VC中的STL要快。比Rouge Wave STL更切合尺度,也更容易移植。Borland C++ Builder已经在其6.0版中插手了对STLport的支持,它利用的STLport就是4.5版的,C++ Builder 6.0同时还提供了STLport的利用说明。你可以在C++ Builder的Include\Stlport子目次下找到所有头文件(好比:C:\Program Files\Borland\Cbuilder6\Include\Stlport)。
STLport最初源于俄国人Boris Fomitchev的一个开拓项目,主要用于将SGI STL的根基代码移植到其他诸如C++Builder可能是Visual C++这样的主流编译器上。因为SGI STL属于开放源码,所以STLport才有权这样做。今朝STLport的最新版本是4.5。可以从如下网站获得更具体的环境先容:
1.4.5 SGI STLhttp://www.sgi.com,可以免费下载其源代码。今朝的最新版本是3.3。
SGI STL是由Silicon Graphics Computer System, Inc公司实现的,其设计者和编写者包罗Alexander Stepanov和Matt Austern,同样它也是HP STL的一个担任版本。它属于开放源码,因此你可以修改和销售它。SGI STL被GCC(linux下的C++编译器)所回收,你可以在GCC的Include子目次下找到所有头文件(好比:C:\cygnus\cygwin-b20\include\g++\include)。由于GCC对C++语言尺度的支持很好,SGI STL在linux平台上的机能相当精彩。另外,其源代码的可读性也很好。可以从如下网站获得更具体的环境先容:
图2:STL家属的谱系
2 牛刀小试:且看一个简朴例程2.1 引子2.2 例程实作2.2.1 第一版:史前时代–转木取火2.2.2 第二版:家产时代–组件化大出产2.2.3 第三版:唯美主义的精品2.3 汗青的评价2.4 如何运行
假如你是一个纯粹的实用主义者,也许一开始就可以从这里开始看起,因为此处提供了一个示例措施,它可以带给你有关利用STL的最直接的感觉。是的,与其纸上谈兵,不如单刀直入,实际操纵一番。可是,需要提醒的是,如果你在兴致昂然地细细咀嚼本章内容的时候,可以或许同时团结前面章节作为佐餐,那将是再好不外的。你会发明,前面所提到的有关STL的那些利益,在此处获得了确切的应证。本章的后半部门,将为你演示在一些主流C++编译器上,运行上述示例措施的详细操纵要领,和需要留意的事项。
很是遗憾,我不得不舍弃"Hello World"这个经典的典型,尽量它不但一次的被各类先容计较机语言的教科书所引用,险些成为了一个默认的“尺度”。其原因在于它过分简朴了,以至于不具备代表性,无法揭示STL的庞大魅力。我选用了一个稍稍巨大一点的例子,它的大抵成果是:从尺度输入设备(一般是键盘)读入一些整型数据,然后对它们举办排序,最终将功效输出到尺度输出设备(一般是显示器屏幕)。这是一种典范的处理惩罚方法,措施自己具备了一个系统所应该具有的险些所有的根基特征:输入 + 处理惩罚 + 输出。你将会看到三个差异版本的措施。第一个是没有利用STL的普通C++措施,你将会看到完成这样看似简朴的工作,需要花多大的力气,并且还未必没有一点问题(真是吃力不奉迎)。第二个措施的主体部门利用了STL特性,此时在第一个措施中所碰着的问题就根基可以办理了。同时,你会发明回收了STL之后,措施变得简捷明快,清晰易读。第三个措施则将STL的成果发挥到了及至,你可以看到措施里险些每一行代码都是和STL相关的。这样的时机并不老是到处可见的,它揭示了STL中的险些所有的根基构成部门,尽量这看起来好像有点过度了。
有几点是需要说明的:
#p#分页标题#e#
这个例程的目标,在于向你演示如安在C++措施中利用STL,同时但愿通过实践,证明STL所带给你简直确实实的长处。措施顶用到的一些STL根基组件,好比:vector(一种容器)、sort(一种排序算法),你只需要有一个大抵的观念就可以了,这并不影响阅读代码和领略措施的寄义。
许多人对GUI(图形用户界面)的运行方法很感乐趣,这也难怪,大度的界面老是会令人赏心好看的。可是很惋惜,在这里没有插手这些成果。这很容易表明,对付所提供的这个简朴示例措施而言,插手GUI特性,是有点舍本逐末的。这将会使措施的代码量骤然间急剧膨胀,而真正可以说明问题的焦点部门确被沉没在诸多无关紧急的代码中间(你需要花去极大的精神来处理惩罚键盘可能鼠标的动静响应这些繁琐而又较为类型的工作)。纵然你有像Borland C++ Builder这样的基于IDE(集成化开拓情况)的东西,界面的处理惩罚变得较为简朴了(框架代码是自动生成的)。请留意,我们这里所谈及的是属于C++尺度的一部门(STL的第一个字母说明白这一点),它不涉及详细的某个开拓东西,它是险些在任何C++编译器上都能编译通过的代码。究竟,在Microsoft Visual C++和Borland C++ Builder里,有关GUI的处理惩罚代码是纷歧样的。假如你想相识这些GUI的细节,这里恐怕没有你但愿获得的谜底,你可以寻找其它相关书籍。
在STL还没有诞生的"暗中时代",C++措施员要完成前面所提到的那些成果,需要做许多工作(不外这比起C措施来,好像好一点),措施大抵是如下这个样子的:
// name:example2_1.cpp
// alias:Rubish
#include <stdlib.h>
#include <iostream.h>
int compare(const void *arg1, const void *arg2);
void main(void)
{
const int max_size = 10; // 数组答允元素的最大个数
int num[max_size]; // 整型数组
// 从尺度输入设备读入整数,同时累计输入个数,
// 直到输入的长短整型数据为止
int n;
//zbf讲明:利用cin输入数据时,中间用空格离隔!输入竣事时先Enter然后Ctrl+z可能按Tab键,然后回车
for (n = 0; cin >> num[n]; n ++);
// C尺度库中的快速排序(quick-sort)函数
qsort(num, n, sizeof(int), compare);
// 将排序功效输出到尺度输出设备
for (int i = 0; i < n; i ++)
cout << num[i] << "\n";
}
// 较量两个数的巨细,
// 假如*(int *)arg1比*(int *)arg2小,则返回-1
// 假如*(int *)arg1比*(int *)arg2大,则返回1
// 假如*(int *)arg1便是*(int *)arg2,则返回0
int compare(const void *arg1, const void *arg2)
{
return (*(int *)arg1 < *(int *)arg2) ? -1 :
(*(int *)arg1 > *(int *)arg2) ? 1 : 0;
}
这是一个和STL没有丝毫干系的传统气势气魄的C++措施。因为措施的注释已经很详尽了,所以不需要我再做更多的表明。总的说来,这个措施看起来并不十分巨大(原来就没有太多成果)。只是,谁人compare函数,看起来有点费劲。指向它的函数指针被作为最后一个实参传入qsort函数,qsort是C措施库stdlib.h中的一个函数。以下是qsort的函数原型:
void qsort(void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
看起来有点令人作呕,尤其是最后一个参数。或许的意思是,第一个参数指明白要排序的数组(好比:措施中的num),第二个参数给出了数组的巨细(qsort没有足够的智力预知你传给它的数组的实际巨细),第三个参数给出了数组中每个元素以字节为单元的巨细。最后谁人长长的家伙,给出了排序时较量元素的方法(照旧因为qsort的智商问题)。
以下是某次运行的功效:
输入:0 9 2 1 5
输出:0 1 2 5 9
#p#分页标题#e#
有一个问题,这个措施并不像看起来那么结实(Robust)。假如我们输入的数字个数高出max_size所划定的上限,就会呈现数组越界问题。假如你在Visual C++的IDE情况下以节制台方法运行这个措施时,会弹出犯科内存会见的错误对话框。
这个问题很严重,严重到足以使你开始从头审视这个措施的代码。为了补充措施中的这一缺陷。我们不得不思量回收如下三种方案中的一种:
回收大容量的静态数组分派。
限定输入的数据个数。
回收动态内存分派。
第一种方案较量简朴,你所做的只是将max_size改大一点,好比:1000可能10000。可是,严格讲这并不能最终办理问题,隐患仍然存在。如果有人足够耐性,照旧可以使你的这个颠末更正后的措施瓦解的。另外,分派一个大数组,凡是是在挥霍空间,因为大大都环境下,数组中的一部门空间并没有被操作。
再来看看第二种方案,通过在第一个for轮回中插手一个限定条件,可以使问题获得办理。好比:for (int n = 0; cin >> num[n] && n < max_size; n ++); 可是这个方案同样不甚抱负,尽量不会使措施瓦解,但失去了机动性,你无法输入更多的数。
看来只有选择第三种方案了。是的,你可以操作指针,以及动态内存分派妥善的办理上述问题,而且使措施具有精采的机动性。这需要用到new,delete操纵符,可能陈腐的malloc(),realloc()和free()函数。可是为此,你将牺牲措施的简捷性,使措施代码陡增,代码的处理惩罚逻辑也不再像原先看起来那么清晰了。一个compare函数或者就已经令你不耐心了,更况且要实现这些巨大的处理惩罚机制呢?很难担保你不会在处理惩罚这个问题的时候堕落,许多措施的bug往往就是这样发生的。同时,你还应该感激stdlib.h,它为你提供了qsort函数,不然,你还需要本身实现排序算法。假如你用的是冒泡法排序,那效率就不会很抱负。……,问题真是越来越让人头疼了!
关于第一个措施的接头就到此为止,假如你对第三种方案感乐趣的话,可以实验着本身编写一个措施,作为思考题。这里就禁绝备再挥霍笔墨去实现这样一个让人不甚愉快的措施了。
我们应该名誉本身所糊口的年月。家产时代,科技的成长所带来的庞大便利已经影响到了我们糊口中的每个细节。假如你还在以原始人类的方法糊口着,那我真该猜疑你是否属于某个糊口在非洲可能南美森林里的原始部落中的一员了,莫非是玛雅文明又重现了?
STL即是这个时代的产品,正如其他科技成就一样,C++措施员也应该尽力使本身适应并充实操作这个"高科技成就"。让我们从头审视第一版的谁人破烂不堪的措施。试着利用一下STL,看看结果如何。
// name:example2_2.cpp
// alias:The first STL program
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void main(void)
{
vector<int> num; // STL中的vector容器
int element;
// 从尺度输入设备读入整数,
// 直到输入的长短整型数据为止
while (cin >> element)
num.push_back(element);
// STL中的排序算法
sort(num.begin(), num.end());
// 将排序功效输出到尺度输出设备
for (int i = 0; i < num.size(); i ++)
cout << num[i] << "\n";
}
这个措施的主要部门改用了STL的部件,看起来要比第一个措施简捷一点,你已经找不到谁人讨厌的compare函数了。它真的能很好的运行吗?你可以试试,因为措施的运行功效和前面的大抵差不多,所以在此略去。我可以向你担保,这个措施是足够结实的。不外,大概你还没有完全看大白措施的代码,所以我需要为你表明一下。究竟,这个戏法变得太快了,较之第一个措施,一眨眼的工夫,那些老的C++措施员所熟悉的代码都不见了,取而代之的是一些新鲜玩意儿。
措施的前三行是包括的头文件,它们提供了措施所要用到的所有C++特性(包罗输入输出处理惩罚,STL中的容器和算法)。不必在意谁人.h,并不是我的疏忽,措施担保可以编译通过,只要你的C++编译器支持尺度C++类型的相关部门。你只需要把它们看作是一些普通的C++头文件就可以了。事实上,也正是如此,假如你对这个变革细节感乐趣的化,可以寄望一下你身旁的佐餐。
同样可以忽略第四行的存在。插手谁人声明只是为了表白措施引用到了std这个尺度名字空间(namespace),因为STL中的那些玩意儿全都包括在那内里。只有通过这行声明,编译器才气答允你利用那些有趣的特性。
#p#分页标题#e#
措施顶用到了vector,它是STL中的一个尺度容器,可以用来存放一些元素。你可以把vector领略为int [?],一个整型的数组。之所以巨细未知是因为,vector是一个可以动态调解巨细的容器,当容器已满时,假如再放入元素则vector会暗暗扩大本身的容量。push_back是vector容器的一个类属成员函数,用来在容器尾端插入一个元素。main函数中第一个while轮回做的工作就是不绝向vector容器尾端插入整型数据,同时自动维护容器空间的巨细。
sort是STL中的尺度算法,用来对容器中的元素举办排序。它需要两个参数用来抉择容器中哪个范畴内的元素可以用来排序。这里用到了vector的另两个类属成员函数。begin()用以指向vector的首端,而end()则指向vector的结尾。这里有两个问题,begin()和end()的返回值是什么?这涉及到STL的另一个重要部件–迭代器(Iterator),不外这里并不需要对它做具体相识。你只需要把它看成是一个指针就可以了,一个指向整型数据的指针。相应的sort函数声明也可以看作是void sort(int* first, int* last),尽量这实际上很不准确。另一个问题是和end()函数有关,尽量前面说它的返回值指向vector的结尾,但这种说法不能算正确。事实上,它的返回值所指向的是vector中最结尾元素的后头一个位置,即所谓pass-the-end value。这听起来有点费解,不外不必在意,这里只是稍带一提。总的来说,sort函数所做的工作是对谁人准整型数组中的元素举办排序,一如第一个措施中的谁人qsort,不外比起qsort来,sort好像要简朴了很多。
措施的最后是输出部门,在这里vector完全可以以假乱真了,它所提供的对元素的会见方法的确和普通的C++内建数组一模一样。谁人size函数用来返回vector中的元素个数,就相当于第一个措施中的变量n。这两行代码直观的不消我再多表明白。
我想我的耐性讲授应该可以使你大抵看懂上面的措施了,事实上STL的运用使措施的逻辑越发清晰,使代码更易于阅读。试问,有谁会不大白begin、end、size这样的字眼所表达的寄义呢(除非他不懂英语)?试着运行一下,看看结果。再试着多输入几个数,看看是否会产生数组越界现象。实践证明,措施运行精采。是的,由于vector容器自行维护了自身的巨细,C++措施员就不消劳神动态内存分派了,指针的错误利用究竟会带来许多贫苦,同时措施也会变得冗长无比。这正是前面第三种方案的缺点地址。
再仔细审视一下你的第一个STL版的C++措施,回首一下第一章所提到的那些有关STL的利益:易于利用,具有家产强度……,再较量一下第一版的措施,我想你应该有所体会了吧!—www.bianceng.cn
事态的成长有时候总会趋向极度,这在那些唯美主义者傍边犹是如此。首先声明,我并不是一个唯美主义者,提供第二版措施的改造版,完全是为了让你更深刻的感觉到STL的魅力地址。在看完第三版之后,你会强烈感觉到这一点。或者你也会酿成一个唯美主义者了,至少在STL方面。这应该不是我的错,因为抉择权在你手里。下面我们来看看这个绝版的C++措施。
// name:example2_3.cpp
// alias:aesthetic version
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
void main(void)
{
typedef vector<int> int_vector;
typedef istream_iterator<int> istream_itr;
typedef ostream_iterator<int> ostream_itr;
typedef back_insert_iterator< int_vector > back_ins_itr;
// STL中的vector容器
int_vector num;
// 从尺度输入设备读入整数,
// 直到输入的长短整型数据为止
copy(istream_itr(cin), istream_itr(), back_ins_itr(num));
// STL中的排序算法
sort(num.begin(), num.end());
// 将排序功效输出到尺度输出设备
copy(num.begin(), num.end(), ostream_itr(cout, "\n"));
}
在这个措施里险些每行代码都是和STL有关的(除了main和那对花括号,虽然尚有注释),而且它包括了STL中险些所有的各大部件(容器container,迭代器iterator, 算法algorithm, 适配器adaptor),独一的遗憾是少了函数工具(functor)的身影。
还记得开头提到的一个典范系统所具有的根基特征吗?–输入+处理惩罚+输出。所有这些成果,在上面的措施里,仅仅是通过三行语句来实现的,个中每一行语句对应一种操纵。对付数据的操纵被高度的抽象化了,而算法和容器之间的组合,就像搭积木一样轻松自如,系统的耦合度被降到了极低点。这就是闪耀着泛型之光的STL的伟大力大举量。如此简捷,如此巧妙,如此神奇!就像把戏一般,以至于再一次让你摸不着脑子。怎么实现的?为什么在看第二版措施的时候如此清晰的你,又坠入了五里雾中(窃喜)。
#p#分页标题#e#
请寄望此处的标题(唯美主义的精品),在实际情况中,你未须要做到这样完美。究竟优美愿望的破灭,在糊口中时常会产生。过于抱负化,并不是一件功德,至少我是这么认为的。正如前面提到的,这个措施只是为了展示STL的奇特魅力,你不得不为它的精彩表示所折服,也许只有深谙STL之道的人才会想出这样的玩意儿来。假如你只是一般性的利用STL,做到第二版这样的水平也就可以了。
实在是因为这个措施过分"简朴",以至于我无法必定,在你还没有完全把握STL之前,通过我的讲授,是否可以或许了解这区区三行代码,我将尽我的最大尽力。
前面提到的迭代器可以对容器内的任意元素举办定位和会见。在STL里,这种特性被加以推广了。一个cin代表了来自输入设备的一段数据流,从观念上讲它对数据流的会见成果雷同于一般意义上的迭代器,可是C++中的cin在许多处所操纵起来并不像是一个迭代器,原因就在于其接口和迭代器的接口纷歧致(好比:不能对cin举办++运算,也不能对之举办取值运算–即*运算)。为了办理这个抵牾,就需要引入适配器的观念。istream_iterator即是一个适配器,它将cin举办包装,使之看起来像是一个普通的迭代器,这样我们就可以将之作为实参传给一些算法了(好比这里的copy算法)。因为算法只认得迭代器,而不会接管cin。对付上面措施中的第一个copy函数而言,其第一个参数展开后的形式是:istream_iterator(cin),其第二个参数展开后的形式是:istream_iterator()(假如你对typedef的语法不清楚,可以参考有关的c++语言书籍)。其结果是发生两个迭代器的姑且工具,前一个指向整型输入数据流的开始,后一个则指向"pass-the-end value"。这个函数的浸染就是将整型输入数据流从新至尾逐一"拷贝"到vector这个准整型数组里,第一个迭代器从开始位置每次累进,最后达到第二个迭代器所指向的位置。或者你要问,假如谁人copy函数的行为真如我所说的那样,为什么不写成如下这个样子呢?
copy(istream_iterator<int>(cin), istream_iterator<int>(), num.begin());
你确实可以这么做,可是有一个小小的贫苦。还记得第一版措施里的谁人数组越界问题吗?假如你这么写的话,就会碰着雷同的贫苦。原因在于copy函数在"拷贝"数据的时候,假如输入的数据个数高出了vector容器的范畴时,数据将会拷贝到容器的外面。此时,容器不会自动增长容量,因为这只是简朴地拷贝,并不是从结尾插入。为了办理这个问题,另一个适配器back_insert_iterator登场了,它的浸染就是引导copy算法每次在容器结尾插入一个数据。措施中的谁人back_ins_itr(num)展开后就是:back_insert_iterator(num),其结果是生成一个这样的迭待器工具。
终于将讲完了三分之一(真不容易!),亏得第二句和前一版措施没有不同,这里就略过了。至于第三句,ostream_itr(cout, "\n")展开后的形式是:ostream_iterator(cout, "\n"),其结果是发生一个处理惩罚输出数据流的迭待器工具,其位置指向数据流的起始处,而且以"\n"作为支解符。第二个copy函数将会从新至尾将vector中的内容"拷贝"到输出设备,第一个参数所代表的迭代器将会从开始位置每次累进,最后达到第二个参数所代表的迭代器所指向的位置。
这就是全部的内容。
汗青的车轮老是滔滔向前的,家产时代的文明较之史前时代,虽然是先进而且发家的。回首那两个时代的C++措施,你会真切的感觉到这种不同。简捷易用,具有家产强度,较好的可移植性,高效率,加之第三个令人眼花的绝版措施所浮现出来的高度抽象性,高度机动性和组件化特性,使你对STL背后所蕴含的泛型化思想都有了些微的感觉。
真幸运,你可以凌驾两个时代,有时机目击这种"文明"的差别。同时,这也应该使你越加刚强信念,使本身顺应时代的潮水。
在你还没有真正开始运行前面后两个措施之前,最好先欣赏一下本节。这里简朴先容了在特定编译器情况下运行STL措施的一些细节,并提供了一些大概碰着的问题的办理步伐。
#p#分页标题#e#
此处,我选用了今朝在Windows平台下较为常见的Microsoft Visual C++ 6.0和Borland C++ Builder 6.0作为例子。尽量Visual C++ 6.0对最新的ANSI/ISO C++尺度支持的并不是很好。不外据称Visual C++ .NET(也就是VC7.0)在这方面的机能有所改进。
你可以选用多种方法运行前面的措施,好比在Visual C++下,你可以直接在DOS呼吁行状态下编译运行,也可以在VC的IDE下回收节制台应用措施(Console Application)的方法运行。对付C++ Builder,环境也雷同。
对付Visual C++而言,假如是在DOS呼吁行状态下,你首先需要找到它的编译器。假定你的Visual C++装在C:\Program Files\Microsoft Visual Studio\VC98下面,则其编译器地址路径应该是C:\Program Files\Microsoft Visual Studio\VC98\Bin,在哪里你可以找到cl.exe文件。编译时请加上/GX和/MT参数。假如一切正常,功效就会发生一个可执行文件。如下所示:
cl /GX /MT example2_2.cpp
前一个参数用于奉告编译器答允异常处理惩罚(Exception Handling)。在P. J. Plauger STL中的许多处所利用了异常处理惩罚机制(即try…throw…catch语法),所以应该加上这个参数,不然会有如下告诫信息:
warning C4530: C++ exception handler used, but unwind semantics are not enabled.
后一个参数则用于使措施支持多线程,它需要在链接时利用LIBCMT.LIB库文件。不外P. J. Plauger STL并不是线程安详的(thread safety)。假如你是在VC情况下利用像STLport这样的STL实现版本,则需要加上这个参数,因为STLport是线程安详的。
假如在IDE情况下,可以在新建工程的时候选择节制台应用措施。
图3:在Visual C++ IDE情况下运行STL措施
至于那些参数的配置,则可以通过在Project成果菜单项中的Settings成果【Alt+F7】中配置编译选项来完成。
图4:在Visual C++ IDE情况下配置编译参数
有时,在IDE情况下编译STL措施时,大概会呈现如下告诫信息(前面那几个示例措施不会呈现这种环境):
warning C4786: ‘……’ : identifier was truncated to ‘255’ characters in the debug information
这是因为编译器在Debug状态下编译时,把措施中所呈现的标识符长度限制在了255个字符范畴内。假如高出最大长度,这些标识符就无法在调试阶段查察和计较了。而在STL措施中大量的用到了模板函数和模板类,编译器在实例化这些内容时,展开之后所发生的标识符往往很长(没准会有一千多个字符!)。假如你想认识一下这个warning的话,很简朴,在措施里加上如下一行代码:
vector<string> string_array; // 雷同于字符串数组变量
对付这样的warning,虽然可以置之不理,不外也是有办理步伐的。 你可以在文件开头插手下面这一行:#pragma warning(disable: 4786)。它强制编译器忽略这个告诫信息,这种做法固然有点卤莽,可是很有效。
(留意:zbf):利用右键点击项目工程中的该cpp文件,选择setting,在c/c++栏,选择PreCompiled headers,然后配置第一选项,选择不利用预编译头!不然会呈现如下问题:
fatal error C1010: unexpected end of file while looking for precompiled header directive
至于C++ Builder,其DOS呼吁行状态下的运行方法是这样的。如果你的C++ Builder装在C:\Program Files\Borland\CBuilder6。则其编译器地址路径应该是C:\Program Files\ Borland\CBuilder6\Bin,在哪里你可以找到bcc32.exe文件,输入如下呼吁,即大功告成了:
bcc32 example2_2.cpp
至于IDE情况下,则可以在新建应用措施的时候,选择节制台领导(Console Wizard)。
图5:在C++ Builder IDE情况下运行STL措施
此刻你可以在你的呆板上运行前面的示例措施了。不外,请恕我多嘴,有些细节不得不提请你留意。小心编译器给你留下的陷阱。好比前面第三个措施中有如下这一行代码:
typedef back_insert_iterator< int_vector > back_ins_itr;
请寄望">"前面的空格,最好不要省去。假如你怜惜这点空格所占用的磁盘空间的话,那就太不划算了。其原因照旧在于C++编译器自己的缺陷。上述代码,相当于如下代码(编译器做的也正是这样的翻译事情):
typedef back_insert_iterator< vector<int> > back_ins_itr;
#p#分页标题#e#
假如你没有加空格的话,编译器会把">>"误认为是单一标识(看起来很像谁人数据流输入操纵符">>")。为了回避这个困难,C++要求利用者必需在两个右尖括号之间插入空格。所以,你最好照旧老诚恳实照我的话做,以制止不须要的贫苦。不外有趣的是,对付上述那行展开前的代码,在Visual C++里纵然你没有加空格,编译器也不会报错。而同样的代码在C++ Builder中没有那么幸运了。不外,最好照旧不要心存荣幸,假如你回收展开后的书写方法,则两个编译器都不会给你包涵面了。
好了,请原谅我的絮叨,此刻你可以亲身感觉一下STL所带给你的真正奇特魅力了,祝你好运!
文章来历: http://windshowzbf.bokee.com/2670911.html