拿起本书,你很可能正在揣摩这本书是否有助于你学习C++。为了做出选择,你需要知道本书将会讲些什么内容,以及怎样不同于堆积在书商书架的其他许多书。我并没打算宣称本书独一无二地优于所有其他类似书籍,但我敢说除了技术精确之外,本书在其他方面也与众不同。.
第一个问题:你已经能够使用某种程序语言编程了吗?如果你的答案是不能,那么几乎可以肯定,本书并非一本适合你起步的书籍。你可能需要某些其他的书,例如我的《You Can Do It!》[Glassborow 2004]。然而,如果你已经了解,诸如循环、判断语句,以及函数等概念,那么请继续阅读本书。
我尽可能少地限定读者需要知道的编程知识,特别是,我不要求读者已经了解C语言的编程知识。如果事实上你已经熟悉C语言,那么请你准备好学习以不同的方式做事情。虽然“C++”这个名字暗示着用C++能够超过C,并且几乎所有的C也都是C++,但是优秀的C程序通常并不是优秀的C++程序。两种语言共享一个公共核心,但二者的差异导致了迥异的编程风格。
我将为你介绍用C++进行编程,这会用到过去20年中发展起来的所有现代惯用法和工具。出于这个原因,本书使用高层特性介绍C++,仅当确有所需要时才会讨论其低层特性。
本书的目的是使你牢固掌握标准C++的基础知识。为了加深理解,你需要动手编写程序。因此,你可以尝试更富有冒险性的程序。我以自己的库补充了C++标准库,我的库提供了以下四项基本的附加功能:
·一些简单工具函数(utility function),使得从一开始就更容易编写出正确的程序。它们用纯粹的标准 C++写成,因此可以被移植到所有标准 C++实现。
·一个可以从纯控制台程序(以纯文本形式运行的程序)中控制的非常原始的图形窗口。它可以一致地运行在基于Windows和Linux的PC之上(以及在OS X版本的Apple Mac之上)。
·一个“一键鼠标”(也就是对所有按钮一视同仁的鼠标)。最大的好处是,该鼠标不具有因为处理多个按钮、滚轮等而产生的复杂性。
·直接键盘读取,于是你的程序就可以直接监测键盘按键。这一小特性戏剧性地扩展了你所能编写的程序的种类。
注意,我提供这四项功能是为了使学习C++的过程更为愉快,与没有它们的情况相比较,你可以编写出更广泛的程序。(如果你愿意,你甚至可以编写出某个经典的控制台游戏,例如Space Invaders)。在本书的大部分内容中,我都避免显式地使用我的库,但你应该可以自由地使用它,既可以用来增强针对所提供的练习的解决方案,也可以添加到你自己的其他练习中去。
除了我自己的工具库以外,本书还附有一款完整的C++编译器(MinGW)以及在两款IDE之间选择的机会。第一款为MinGW Developer Studio,它是用户支持(user-supported)的产品。如果这对你来说是个生词,那么我告诉你,它意味着如果你喜欢某产品并打算继续使用的话,你最好能为该产品的作者捐点款(不论你认为对你而言这是否值得)。Pxviii它不是共享软件,因为不存在限制性许可证来限定在不付费的情况下能使用什么功能。它也不是免费软件,免费软件从来就不期望你付给谁一分钱(MinGW本身是一个杰出的免费软件产品的榜样)。那么,请登录产品的网站并捐款,以表示你对一个构造优良的工具的感激之情。
第二个可供选择的IDE是更复杂但高度可移植的JGraspTM,它来自奥本大学(Auburn University)。它用Java写成,因此你需要一个适当的Java运行时环境。这款IDE为教学目的而编写。它拥有许多卓越的功能以帮助新手探索自己所写的代码。它对个人免费,但是不能作为商业产品的一部分分发。包含在本书光盘中的许可证是教育性书籍专用的。JGrasp接受许多C++编译器(包括MinGW在内)作为插件程序。
这些IDE既可以运行于微软Windows之上,也可以运行在Linux系统上。实际上,JGrasp能够在支持Java的任何系统上运行,但就本书而言,仅限于那些能实现我的库的系统。在本书写作期间,这些系统包括Windows 95之后所有版本的Windows和Linux(以及我前面说过的Apple Mac上的OS X)。
本书的正文部分假定你使用的是以MinGW作为编译器的MinGW Developer Studio。该工具免费提供于随书光盘中,它是职业程序员通常使用的重量级的专业工具。你也可能希望使用其他一些工具集(toolset),不过在此之前你需要检查网站(http://www.spellen.org/youcandoit/),看看我是否针对那套工具提供了一份库的副本。遗憾的是,C++的局限性之一是分发的库常常特定于编译器,这就意味着,如果你使用的是一款不同的编译器,就得重新编译库。
我将本书设计为一本可当作参考书来使用的书。举个例子,当我介绍C++内建类型(built-in type)的时候,学习文块(study text)将只使用到可用类型的一个子集。而在该章的末尾,你会发现一个关于所有内建类型以及其派生物的整合集。该整合集被放置于靠近你初次学习C++类型系统的地方(而不是作为附录存在),它提供了集中于一处的参考,以便你查阅。初次学习那一章时,你可能会跳过参考部分,但在稍后某个阶段你也许希望深入阅读其中某个主题,或方便地查阅什么东西。
总结一下,本书教你使用标准C++以现代惯用风格进行编程。为使学习的过程更令人愉快,我提供了一些对C++库的扩展,最起码可以在微软Windows(98及后续版本)和Linux之间移植。本书带有你阅读和学习所需要的每样东西,就缺一台电脑和操作系统了——那是你必须自行准备的。除了一些扩展支持(图形、声音、鼠标以及直接键盘读取)外,本书中其他所有东西都可以移植到任何安装了标准C++编译器的计算机上。
学习C++
有时候环境迫使我们通过自学获得新技能。这条路常常很难走,而且有时候会妨碍我们的进展。如果你想要学习演奏一种乐器,你需要自己练习,但花时间与其他人一起练习也是不可或缺的。否则,你会养成不良习惯,这将严重地妨碍你将来的进步,不利于同他人一起制作音乐。同样的道理也适用于学习计算机语言编程。你很容易就会将自己第一门语言的惯用法强加到你正在学习的新语言(C++)上。如果缺乏与他人协作的训练,你对C++这种不正确的使用不会暴露出来。若你的第一门语言是C++的近亲(例如C、C#或Java),情况尤其如此。仅仅学习语言的语法是远远不够的,你还需要学习应当如何使用该语法。
在上学时,我从一本书中学会了围棋。我向几个小伙伴传授规则,并在一个自制的木板上玩此游戏。许多年后,我遇见某人,他曾跟一名经验丰富的棋手学过围棋。起初几盘,对我们而言都很有意思,因为我对战术和战略的理解和运用是完全随意的。有些地方我玩得相对合理,而在其他地方我又犯新手的错误。千万别让你的C++也变成那样。
你最好寻找一个学习伙伴和一位导师。前者会在语言上为你提供一个可供参考的观点;后者将帮助你形成有效透彻的理解。通过与老手交流,可确保你弄清楚事物的运作机理。警惕那些自封的专家。与我早期学习围棋知识一样,他们的知识可能毫无章法。访问下面这些有益的地方,阅读并提问:
.comp.lang.c++.moderated
alt.comp.lang.learn.c-c++
那些Usenet新闻组有常驻的专家,他们比绝大多数人更懂C++。在那里,你可以找到真正的专家,充分利用他们。
学习C++是一项交互式练习。不实际编写程序就无法学会C++。从任何其他一种你所学会的语言中,你应该已经认识到了这一点,在这里我将它作为重点进行重申。如果你跳过本书中给出的实际操作,你就是在自讨苦吃。虽然你可能会更快速地读完本书,但你的C++水平将会因此而更糟糕。本书并不是一本“在某段规定时间内学会C++”的书,你需要投入较长的时间,直到你掌握每一个知识点为止。有时也许值得跳过一个关键的知识点,稍后再回过头来学习。但是如果因为你不理解,或你认为它没什么用,而不断地跳过某些东西,反过来它就会找你麻烦。
如何使用本书
请在一台计算机面前使用本书!试验代码并做练习。我希望练习对你有所帮助,我提供的练习并非是为了填满教科书中的空白。有时候像这样学习极具诱惑:看看练习,判定自己可以想到该怎么做,然后不实际做练习就继续前进。然而,动手实践效果往往更佳,这将有助于你熟练使用C++。通常情况下,做练习将会暴露你第一眼扫过时漏掉的微妙之处。
在其他时候,你可能会为一个新程序或为从书中发展一个程序而得到这一妙策。在这种情况下,屈服于那个诱惑,花些时间执行这一想法。如果你详细描述问题,并email给我(最初经由书籍网站上的提交表单),我会将其公布在网站上,以启示他人。
会在随书CD上发现一些源代码,其中有些是我的库的源代码,也有些来自其他地方。某些源代码是好的,而某些却不是,但也囊括在内,因为我要使用它当作垫脚石。比方说,用以支持我的库中Portable Network Graphics部分的代码是用极其古老的C编写的。它最大的优点在于它总是能够正确运行,但它远远背离我鼓励你编写的那种干净的、可维护的代码。CD上的其他源代码要求大量的特定于Windows或Linux的知识。即使对拥有相关系统良好背景的程序员而言,也远远算不上清晰。甚至CD上我自己的代码与优秀代码相比也是远非完美的。随着时间的推移和其他专家的评论,我的代码有所改善。所有的代码都要得到或至少应该得到“逐步改善”。需要应用于代码的测试之一便是:代码是否具有良好的结构以便使得这种“逐步改善”相对容易一些。
要想透彻理解playpen.cpp中的代码,你需要丰富的C++经验,以及丰富的针对所用OS的图形用户界面的编程经验。若希望使我的库适合于Linux和支持X Windows System(别与微软Windows混淆了,那是完全不同的系统)的其他系统,就需要改写那个文件。
你可以从我的配书网站(http://www.spellen.org/youcandoit/)获得其他源代码。你应该访问该网站寻找任何随书CD中可能遗漏的东西。另外,你可以通过这个网站联系我,或直接发送E-mail至ycpcpp@robinton.demon.co.uk。如果你选择直接给我发E-mail,请确保你包含了主题行,因为通常我将不含主题的E-mail认为是垃圾邮件。
本书并非“x天学会C++”的那一种。你必须设定你自己的计划。如果你只花了六个月就掌握了我提供的全部东西,那么你做得很棒。即便在你学完本书很久之前,就拥有足够的C++知识来做许多有用的事情。另一方面,即便在你学习本书之后,还有更多的东西有待学习。每一位优秀的C++专家都告诉我同一件事:他们从未曾停止过学习和探索关于通用编程和特定使用C++编程的新知识。或许永不停歇的学习和探索过程,正是他们成为世界级专家的奥秘。
对注释的注释
有些人觉得应当广泛地给源代码加上注释,你也会发现有些书上显示的源代码中充满了密密麻麻的注释。但我相信,最优秀的代码文档即为代码本身。注释应该用来表达那些在源代码中无法轻易表达的东西。一份对C++的介绍需要提供大量的解释,但依我看来,代码中的注释行并不是干这种事的。
确实有很多有意义的东西是无法用源代码表达的。例如,每一份源代码文件都应该包括其创建日期及作者名字。而且在某些地方需要有文档来描述一段源代码所做的事情。然而,如果代码的每一行上都有注释,这种编码风格就有严重的问题了。适当地放置少许注释可能非常有意义。然而,倘若注释中到处都充斥着毫无价值的重复,那么有价值的注释就可能会被淹没。
C++概观
鉴于本章处在你实际学习使用C++之前,我几乎要将它设为第-1章。如果急于开始学习,你可以跳过本章,以后你还可以再回来阅读。然而,我认为快速地浏览一遍,甚至是浏览你不熟悉的语言部分,也将有助于你的C++学习。
名字的内涵
你也许已经知道C++(发音为“see plus plus”)之所以这样命名,是因为Bjarne Stroustrup将它设计为一门较早的(但仍广泛使用的)名为C的语言的继任者。在C中,++表示“增量”(increment),在数学术语中,增量计算意味获得某物的继任者。因此,你可以认为此名字是“C的继任者”的意思。和继任者在数学中的概念一样,那并不意味着取代。如果你已经熟悉C,那么你需要认识到C++是一门全新且不同的语言,即便许多C源代码可以作为C++代码进行编译。通常,用C++编译器成功编译C源代码的结果,与由C编译器产生的程序的行为完全一样,然而情况并非总是如此。
在20世纪80年代早期,Bjarne Stroustrup设计了一个对C的扩展,称为“带类的C”(C with classes)。如果你对那段历史感兴趣,希望了解那个个人工具是如何成长为世界上使用最广泛、激发了无数人想像力的编程语言,那么你需要去别处查找。(一个好的起点是《The Design and Evolution of C++》[Stroustrup 1994]。)该书是关于用C++编程的,所用C++由ISO/IEC 14882:2003标准定义,也就是说,是在2003年定义的标准C++(这是对第一份正式标准的修订版,含有从1998年到2003年之间形形色色的修改)。
C++的内涵
C++是世界上使用最广泛的编程语言之一。它也是人类设计的最庞大的编程语言之一。Bjarne Stroustrup说明该语言的设计标准之一是,在C++和本地机器代码之间不应存在更低层语言的生存空间。很少有程序员使用过C++的最低层特性,很多程序员甚至不知道它拥有关键字asm(该关键字允许你编写汇编代码)。
将C合并入C++是个至关重要的设计抉择。从正面来看,这使得C程序员很容易转移到C++。至少在理论上,先进行所能够的转移,渐渐地再增加他们的C++技巧和对C++的理解。从反面来看,它使C++依赖C设计的许多特性,经验表明那些特性至少是有问题的。这也给许多从C转到C++的程序员造成了问题,因为他们所做的过渡,仅仅是将C编译器换成了C++编译器,却没有从精神上真正地完成从C到C++的过渡。内心里他们依然是C程序员。虽然那不是什么错误,但这的确在他们成为职业C++程序员的道路上设置了障碍。如果你是一名C程序员,你也许会发现假如你的第一门语言是别的什么语言,学习现代C++将不会如此困难。
在C++的高端部分,我们找到了允许创新者进行元编程(metaprogramming)的工具,即可以由源代码生成源代码。我们在本书中将不探究这个问题,但它值得我们注意,你正在学习的这门语言支持当前业界最具创新意义的程序开发。
在汇编程序支持和元编程支持之间,C++为过程式编程(procedural programming)、基于对象编程(object-based programming)、面向对象编程(object-oriented programming)(稍后当你懂得足够的C++知识时,我会解释它们的差异,以便你能充分领会),以及泛型编程(generic programming)提供了工具。如果足够小心谨慎,你甚至可以实现一些函数式编程(functional programming)。
与C++语言核心的原生能力相伴的是,C++标准库对程序员通常要做的一系列广泛的事情提供了支持。在过去几年中我们学到了很多东西,倘若今天开始编写库,则可能打造出一个截然不同的库。然而,我们所拥有的东西要好于以前任何被广泛使用的编程语言所提供的一切。另外,库中很多内容是针对扩展而设计的:该库的设计使得我们可以方便地添加新组件,并且这些新组件可与标准组件正确地协作。另一方面,标准库当前缺乏更为新近的语言(如Java、C#以及Python)的使用者所期盼的组件。
C++没有规定方法论(methodology)或范型(paradigm),这既是其优势之一也是其弱点之一。当某人第一次学习C++时,这可能是个问题,因为可选范围之宽要求他理解那些选择的含意。如果初学者已经熟悉另一种编程语言,他们自然而然地会试图发现如何用C++术语来写出他们的第一种语言代码。他们会用第一种语言思考,然后尝试翻译成C++。C++提供的编程方式种类之多,使得他们通常可以用其编写出酷似别的语言的代码,但那通常不是良好的C++代码。
人人都可以将C++视作自己的第二语言。掌握C++要求你将第一门语言抛之身后。这很困难,一路上你会犯不少错误。然而,无论是使用C++,还是使用任何其他你已经熟悉、或定于以后去学习的编程语言,你都将成为一名更棒的程序员。
C++拥有一套广泛的操作符。它们中的大多数都可被扩展以用于用户自定义类型。既然有了重新定义的潜力,我们就有责任灵活地使用这一工具。C++应当可以添加诸如复数、矩阵、四元数等类型,并提供领域专家期待并会认为符合直觉的操作符。很不幸,有些程序员将这一可用机制当作一种寻找创新用法的挑战。结果,他们的代码变得愈加晦涩难懂。
C++是一门充满活力的语言。这里,我传达了两个意思。第一,最优秀的使用者不断开发新的惯用法及其他用法。C++中元编程的整个成长,始于一个夜晚,一组专家认识到C++的模板(template)技术(设计用于支持泛型编程),凭其本身的能力是图灵完备的(Turing-complete)编程语言,这种机制实现于编译期。C++并非为元编程而设计,所以用它做这种事往往看上去很“丑陋”,但它使得专家能够探索元编程的潜力。
C++充满活力的第二个方面是,它会定期地变化。即便我在写作本书时,那些负责C++定义的组织(WG21,一个ISO标准委员会)正忙于一些变化,它们最终将会在这个十年结束的时候开始生效。那些变化中,有些是为了使C++更易于编写和学习;有些瞄准于清理语言中的不一致情形;有些则针对于进一步扩展该语言的能力。我在写作本书时,不可能精 确预测将会加进什么东西或将会引入什么变化。不过有一点可以确信,提供对元编程更好的支持是对C++可能的加强之一。
我与你分享这些,只是希望你能看见C++充满活力、状态良好,并有序地持续发展,一切尽在掌握。你从本书所学到的知识和技能,将会使用很多年,甚至在下一个C++标准发布后,你所学过的几乎每样东西都将依然适用,但是可能会有更简单的方法可以达到相同的目标。ISO编程语言成长和发展的指导方针之一是,每样成果都应该保护既有源代码。这与某些非ISO编程语言形成鲜明的对比,后者变化周期较短,而且改变经常以破坏既有代码的方式进行。
针对不同背景的读者
下面,我将尝试基于你最有可能学过的语言给出一些有益的评论。每一门语言都有其长处。每一门语言都有其独特的属性。使用这些语言的人们可能已经学习了形形色色的惯用法,其中一些惯用法并不能简单地套用到C++身上。
如果你的第一门语言(例如C、Java或C#)的语法与C++类似,你需要克服“认为看上去相同的东西在行为上也完全一致”的倾向。
一般而言,你需要与这样的一种概念作斗争:认为你的第一门语言工作的方式就是所有编程语言都应该采用的方式。语言之间存在广泛的差异。大多数计算机语言都是由具备高度才智的人设计的。差异并非偶然,差异也不会使得某一种语言优于另一种。有时我们可以利用某一种语言的工作方式而采用另一种语言编写程序。
很多年前,Oxford Schools Sailing Centre的理事邀请我为他编写一个在他的Acorn BBC Microcomputer(一种古老的英国桌面计算机,采用8位6502处理器)上使用的竞赛分析程序。那种机器的本地语言是某个版本的BASIC。当时,我处理问题所选择的语言是Forth(如果你想要了解更多,可访问http://www.forth.org/)。我在理事的房间靠咖啡和饼干填饱肚皮度过了一个周六的上午,肤浅地以BBC BASIC写出了他要的程序。完工时,他看着程序说:“这看起来一点都不像BBC BASIC。”他一语中的:此程序用Forth设计,而用BBC BASIC实现。每一条程序语句都是正确的BBC BASIC语句,但没有哪个BBC BASIC程序员会写出那样的代码。而Forth程序员将会认得它,尽管使用了一种不同的编程语言。
对付临时性的任务,有时可以那么做,但结果既不是好的Forth程序,也不是好的BBC BASIC程序。它只有一点价值:满足了用户的需要并正常工作。如果一个学习BBC BASIC的学生写出那样的程序,他将因对语言的使用不当而被我扣分。记住,掌握一门语言包括掌握该语言的惯用法。
针对C++程序员的基础C++
别误会,我并未失去条理。相当多的读者可能已经用C++进行了一些初级编程,其中,有些人获得了有益的经验和良好的入门,而有些人的入门情况可能并不太理想。..
我的《You Can Do It!》[Glassborow 2004] 是一本特殊的编程书籍,那本书的关注焦点在于如何编程。尽管使用C++作为编程语言,但它并不是作为一本C++书籍而写的,因此省去了大量的关于语言的知识(我估计那本书只涵盖了大约10%的C++。)尽管那本书与本书之间略有重叠,但那些通过我的第一本书学到了基本编程技能的人,可以从这本书中学会关于C++的更多知识。
有很多其他书籍将C++当作第一门语言来介绍。其中有的表现出色,有些相当平庸。在学校里或从基于网络的教程中,你可能也学过C++,无论属于哪一种情况,你可能都需要一个对C++基础更为完整的介绍。和所有那些从某种其他语言中获得了编程基础的本书读者一样,你需要忘却一些东西,并改变对其他东西的关注焦点。如果在阅读本书剩余部分时,遇到了什么东西与以前所学的产生了冲突,你需要努力想一想。也许你早先的学习导致你过分特定于对事物工作方式的某一种解释,也许你原来所学的纯粹是错误的。当然了,也可能是本书叙述有误(尽管我真诚地希望不是这样)。
存在潜在冲突的一个明确的领域是,我在这里描写的C++完全不同于20世纪90年代初的C++,而许多其他书籍介绍的正是后者。语言的语法没有改变(尽管有些增加),但最优秀的现代程序员使用那种语法来表达问题解决方案的方式,已经发生了翻天覆地的变化。
善于利用你以前学到的C++和本书所描述的C++之间的冲突所产生的任何紧张心理。在设法解决那些冲突的过程中,你将对C++的工作方式获得更为透彻的理解。虽然对于编写C++代码和讲授C++的方式,我给出了一大堆想法,但那并不意味着我拥有“唯一正确的方式”——根本就不存在这种方式。就编写C++代码而言,存在很多低劣的方式和一些优秀的方式。优秀方式的特点之一是一致性。如果我选择通过一个不同的惯用法来表达思想,我希望是因为那儿的确存在差异。我努力不这么做:平日采用某种方式做事情,而在周末采用不同的方式行事。
然而,存在一个告诫:程序相异的方面之一在于它们的用途。比方说,本书中多数代码设计出来是用于帮助你学习C++的。它们与作为大型应用程序的组成部分所编写的代码截然不同。当作为团队的成员之一工作时,你应该遵守团队的编码标准。然而,你也应该为你编写的每一行代码感到自豪。我不主张“无私程序设计”,我们应该为自己的贡献感到自豪,但话又说回来,我们也不应该通过树立与团队其他成员不同的风格,来追求过激的贡献。
从优秀的个人代码到工业强度的代码的距离还是相当大的。我们为自己编写代码时常常会忽视这一事实:我们编写的程序将只运行于我们正在使用的机器之上,或者依赖于我们所使用的开发工具。在学习C++的过程中,你应当对标准C++和任何额外的东西有着清晰的区分。我想你将在本书中找到此分界线。
针对C程序员的基础C++
学习C++的传统路线是从学习C开始。遗憾的是,C的许多卓越方面在C++的环境中不再那么合理。在C++中,我们不得不考虑诸如异常安全(exception safety)(耐心些,本书后面会讲到它的!)、多态、const正确性,以及函数和操作符的重载问题。这些问题把我们带入了一种十分不同的编程风格中。
一个小例子是使用指针问题。优秀的C代码往往很依赖于对指针正确而广泛的使用,而优秀的C++代码常常避免使用指针,或使用一个用于封装指针的C++机制(称为智能指针)来控制指针的潜在危害。
另一个例子是,在C++中我们很少(如果有的话)使用动态数组(C程序员使用malloc()创建并使用realloc()进行管理的一种对象)。C++提供了一个自动处理大多数资源问题的向量容器(std::vector[])。
C++为处理char类型的字符串和wchar_t类型的宽字符串提供了真正的类型(wchar_t在C++中是完全的内建类型,而不像在C中那样只是某种整数类型的别名)。它还提供了用于创建其他字符串类型(例如日文字符串类型)的方便机制。
C++输入输出机制被设计为类型安全的,这与C中的输入输出机制不同。在C中,确保实际提供的类型就是程序员想要的类型完全是程序员自己的责任。
学习C++的C程序员需要专注于理解为什么C++以不同的方式做某些事情。与你也许在其他地方看见的“C++不如C”的观点相反,尽管二者在语法上有着故意的兼容性,C++是一门截然不同的语言。如果不能理解这一点,你的C++知识将只是C的伪装,这对你自己和C++都没有什么好处。
C++中没有什么可以阻止你使用C的动态数组、char和wchar_t数组,或I/O函数家族的printf和scanf的成员。C++不过是为程序员提供了一种可以节约时间的替代机制罢了。
有时我写道,对于完成某些事情而言,C++拥有比C更好的机制。我并非暗示C++比C优秀,这样的声明应该放在更广泛的编程上下文中思考。C是一门紧贴机器硬件的基础性编程语言,这使得它成为了编写渗透现代生活的小规模、嵌入式系统(例如许多微控制器)的杰出语言。C++被设计用于编写大规模的程序,这样的程序包含上百万行的代码司空见惯。
针对Java程序员的基础C++
你将要遭遇的难题之一是,大多数Java语法也是有效的C++语法,但所做的事情却略微不同。这两种语言的潜在对象模型有着微妙的(有些人更愿意说是“根本的”)区别。
在某些方面,在学习C++的道路上,你将拥有比别人更大的困难。你会不断地编写你期望能够工作的代码。但编译之后,它却以你意想不到的方式做些古怪的事情。你不要冲动地以为自己的编译器坏掉了,或以为C++是个有毛病的语言。Java具备许多长处,我当然不打算诋毁它(我绝对不参与语言论战)。但如果Java是你学习的第一门语言,当你尝试学习C++时会碰到相当多的问题。
主要的问题包括诸如求值顺序问题:Java严格定义了这一点,C++则规定可以为任何顺序。另一个问题是引用(reference)的概念:C++引用并非简单地以和Java引用一样的方式运作。例如,C++引用被要求指向一个实际存在的对象,并且在自己的生命期内始终指向同一个对象。
你还会发现关于构造函数(constructor)的问题:表面上,C++和Java的构造函数看起来都做相同的事情,但当我们透过现象看本质,便会发现它们以不同的方式工作。在另一端,我们使用析构函数(destructor)来结束一个对象的生命期,但C++没有垃圾回收机制(该语言中没有什么东西可以阻止我们加入垃圾回收机制,只要程序员避开某些C++机制就可以了)。缺乏垃圾回收机制是一项经过深思熟虑的设计决定:我们总是可以加入它,但不能将它拿掉。Java源代码期待一个垃圾回收器,如果没有它,情况将变成一团糟。然而,在没有垃圾回收机制的前提下编写的C++程序,在有垃圾回收机制的情况下通常也可以正确地运行。
所有这些问题和许多其他问题会使事情变得困难重重,因为你需要重新学习代码阅读技能。
我回想起学生时代的一段故事,那时我是一名不错的国际象棋选手。当时(1956年)的英国国际象棋冠军C.H.O誅. Alexander参观我们学校,给我们一些指导并在学校里举行了一场车轮大战。结束时,我们讨论起花式国际象棋(具有形形色色更改过的规则的国际象棋)的话题。他建议的变化之一就是简单地交换棋子的使用规则,例如将“象”当作“马”使用。
如果你会下国际象棋,尝试一下,我想你会理解这个问题,事物并不完全是它们表现出来的样子。这就是Java程序员在学习C++时将遇到的主要问题。
针对C#程序员的基础C++
C#在外表上甚至更为接近C++(“#”符号与重叠的加号之间的相似性并非偶然),因此将增加代码阅读问题。
与Java程序员一样,你需要学习全局函数和全局数据的概念。这是一个艰难的步骤。和Java程序员不同,你的第一门语言包含析构函数和用户自定义值类型(user-defined value types)的思想。然而,C#的析构函数概念与C++的完全不同,你需要动一番脑筋,以理清这些不同之处。
C#拥有垃圾回收机制,并深受这一必要条件约束,即需要运行于基于CLI(Common Language Infrastructure,通用语言基础设施)的虚拟机(例如.NET或Mono)之上。比方说,类型被更严格地定义了—它们不得不这样,因为所有运行于CLI VM之上的语言都必须对基础类型的大小和布局方面达成一致意见。
另外,和Java一样,C#引用并不能精确地等同于C++引用。当我写到“引用”的时候,你一定要当心,它也许并不是你想像中的东西。
在C++中,关键字struct和class的含义非常接近,近乎同义词,它们都用来定义用户类型。二者是如此接近,以至于优秀的C++程序员在任何时候使用struct的唯一原因是,为了强调所编写的基本上是一个简单的C风格聚合类型。这里不是解释为何C++提供两个无实质区别的关键字的地方,尽管某些人认为Bjarne Stroustrup在此处做出了一个欠完美的决定。
在C++中,值类型(C#定义为struct)和对象类型(近似于C#中class类型的概念)的区别完全取决于程序员以怎样的方式定义类型。在C++中选用struct或class并不存在语义上的区别。
针对COBOL程序员的基础C++
对于长期使用COBOL编程的程序员来说,C++带来了难以应付的冲击。我还能记得自己坐在一个介绍性的C++课程的教室后面的情形(当时我正在评估讲师)。有一个学员已经用COBOL写了25年程序。他发现自己非常难以理解指针的概念,因为在用COBOL编程的岁月中,他从未需要过这样一种机制。讲师在尝试引导学员认识“C++需要COBOL所不需要的某些东西”的过程中,遇到了重重困难。在为期一周的课程结束后,该学员评价这个课程纯属浪费时间。在某种意义上对他而言确实如此。这件事情发生之后,我结识了他的经理,经理告诉了我故事的下文。作为帮助他摆脱过于狭隘的编程观点的最后一次努力,该程序员被派去参加听课,这并没有奏效,后果是老板再也不能将他当作程序员使用了,而且顽固不化的思想也使他难以适应公司里的其他工作。
我讲述这个悲哀的故事的目的在于告诫其他人,关于如何编程的观点一旦僵化,职业发展将变得困难并且最终变成不可能。如果你从一个彻底不同的语言来到C++世界,你需要多加留意基础性的东西,例如判断(if-else)、循环(for和do-while)以及函数或过程。所有语言都共享那些基础,但它们的确切实现方式可能是多样化的。让我们来考虑自然语言,英语、中文、阿拉伯语和印地语都是强大的人类语言,但如果你就此认为字母是书写的基础,处理中文时你就会遇到很大的麻烦。
我从小在苏丹长大,我还记得母亲努力教一个仆人(在他的请求之下)读英文的情形。在阿拉伯语中,基本的意思由辅音字母携带,动词、名词、形容词等之间的区别由元音来传达。现在Abdul知道英语是不同的,但知道和相信是两回事。有一天他坐在那儿,努力地想找出“hat”、“hot”、“hate”、“heat”、“hut”、“hit”和“hoot”背后的逻辑。在他看来,hut(小屋)和hat(帽子)之间存在显而易见的关系,当天气hot(热)的时候你戴hat(帽子),由于hot(热)的情绪,“hate(恨)”也有几分合适。仅仅因为可能性,“hit(打)”也配合进来,因为你戴着hat(帽子),既保护你不被太阳的heat(热)伤害,又防止你被hit(打)到head(头)。可是,“hoot(猫头鹰的叫声)”该如何添加进来呢?
谨防试图将你对于某个主题的观点强加进某个预想的框架中。正如在英语中“ht”并不标识一个基础性概念那样,你必须避免试图将C++强加到你的第一门语言所打造的模具中。
针对Python程序员的基础C++
Python是一门非常有意思的语言。然而,那些学习使用Python编程的人必须小心地将编程基础和Python实现它们的方式区分开来。Python有趣的特性之一是程序结构是通过程序布局来表达的。在Python中,缩排的级别至关重要,C++则不然。C++使用花括号“{”和“}”将语句包围在一起。我们鼓励C++程序员使用缩排,以使代码更易于人们阅读,但缩排对编译器没有任何意义。
Python(以及其他少数语言)的另一个重要特性是:名字就只是名字而已。在Python中,名字具有它最后一次被赋予的东西的特征。这是极其强大的编程概念,但是它无法在编译型语言中很好地工作。虽然C++源代码能够被解释执行(我知道有一个不错的C++解释器),但它们通常被编译成适合于目标平台的机器码。这就是为什么为Linux机器编译的C++程序不能运行在微软Windows机器上。
Python通过提供一个Python程序(实际上是一个虚拟机),该程序接受Python源代码作为数据并执行之,以规避这个问题。在这方面,它有点像Java:Java程序只运行于一个名为Java虚拟机的特殊程序的上下文中。然而,Python比Java走得更远,因为它更多地利用了代码的直接性(immediacy)。
我不打算解释每种机制的得与失。我所能做的全部就是警告你它们完全不同。每种语言都有自己的优势,每种语言也都有自己的不足。
针对(Visual)Basic程序员的基础C++
多年以前人们将BASIC(当时,计算机语言全部以大写字母拼写)设计为一门用来教授编程的语言。在过去的岁月里,BASIC受到来自学术界的大量的批评,其中有些批评是有道理的,而有些则不然。最大的问题不在于BASIC自身,而在于通常的教授方式。它使许多老师跌入教授糟糕编程的陷阱之中。
Visual Basic是一门微软私有的语言,是对原始BASIC的一个精化和扩展。它是一门强大的语言,但受到了其拥有者“发行不兼容的方言的倾向”所危害。Visual Basic较好的特性之一是供应了可视化编程工具,它允许你从一个工具箱中,或根据更基本的工具,为复杂的组件生成代码。VB程序员感到惊奇的是,Visual C++(微软为C++编程准备的工具箱)没有为C++提供任何种类的可视化编程工具。尽管存在一些以C++进行可视化编程的工具,但它们的用武之地极其有限,因为C++的强项在于对计算的精确表达和数据管理,而非可视化。
相比较使用可用的组件来构造应用程序而言,C++更多地用于底层编程。当你想要做某些不同的事情的时候,也就是当缺乏预先设计好的组件时,C++便成为一个更强大的工具。
当学习C++的时候,你会关心表达计算性问题的解决方案,而非专注于数据捕获和数据显示。在牢固地掌握了C++基础之后,你能够使用预构建(pre-built)的组件库来处理某些输入输出问题,但那不是C++的关注焦点。你甚至能找到有用的可视化编程工具(尽管它们价格不菲)。
针对Pascal和Delphi程序员的基础C++
Pascal是一门抱有宏伟目标的语言,它意图提供一种难以误用的计算语言。遗憾的是,结果它创造了一代含有一半坏心态的程序员。有着很高天分的程序员知道Pascal妨碍和阻止他们做自己清楚可以做且可以安全地做的事情。这将他们引向一种过去常常叫做“hacking”的创造性编程的形式。换句话说,他们发现了绕过“编译器出于好心的阻碍”的方式。其他学习Pascal的程序员趋向于这么一种观点:如果编译器愿意编译代码,则代码OK。这导致了这样的一种心态:编译器能够编译的,即是安全的。
阅读本书的Pascal程序员不大可能有这样的心态,但我还是要告诫一下:C++要求使用者有责任明白自己在做什么,并避免做危险的事情。C++编译器不会尝试猜测你想要做的事情,它不会因为结果可能有危险就拒绝编译代码。
C++从C继承的更为著名的一个问题是缓冲区溢出(当然,这个问题并非C和C++的专利)。C++希望当你为输入提供存储区的时候,你必须确保提供了足够的存储区,或加入了某种机制用以预防过度的输入覆写(overwrite)其他数据。如果编程时粗心大意,你写出的程序就容易遭受“数据过载”的攻击。身处Internet时代,我们中的许多人都发现了这种疏忽所造成的严重后果。Pascal没有在许多年前就灭绝,这要归功于一个公司所做的努力,这个公司就是Borland。Borland创建了一个带有大量扩展版本的Pascal。那些扩展提供了安全而正确的方式,用以完成程序员想要做却被标准Pascal阻止的事情。
最近几年来,Borland以支持面向对象编程的特性进一步增强了Pascal。Pascal的那个扩展的方言叫做Delphi。另外,Borland还增强了他们的C++开发工具以支持C++与Delphi的混用。这样一来,若你是Delphi程序员,则你必须非常小心,以免当在Borland C++ Builder(他们对C++的实现)的Delphi兼容模式(它十分具有诱惑性,因为那样你就拥有了一个广泛的第三方库的使用权,该库以Delphi编写但可以经由Borland扩展的C++访问)下工作时,误以为那就是C++。
总的来说,Pascal程序员必需学会确保自己的代码正确,而不要指望编译器拒绝危险的构造。
针对函数式程序员的基础C++
如果你有着函数式编程(functional programming)的背景或许学过Haskell、Scheme或ML),你很可能会为C++如此傲慢地称之为函数(function)的东西而震惊。在C++中,不仅允许函数有副作用,而且它们常常如此。或许有一天,我们将拥有一种机制用以告诉C++编译器,某些东西才是真正的数学/函数式编程意义上的函数,但那一天尚未来临。
默认情况下,C++变量确实是变量。像其他编程语言一样,C++允许甚至鼓励赋值。如果你是一名函数式程序员,你首先不得不控制自己可能为这么一个“不守纪律”的语言而产生的本能的反感。
出于学习本书的目的,你只能把自己学过的东西放在一边—但别抛弃它,因为当你继续前进到不再那么基础的C++(不在本书之中,但或许是下一本)时,你将发现你所熟悉的许多概念和惯用法,会使你成为在泛型编程(generic programming)和元编程(metaprogramming)的高级方面的熟练使用者。实际上,顶尖的C++程序员为了增强他们的C++技能,往往特意选学一种像Haskell这样的语言。
针对Lisp和Logo程序员的基础C++
如果你已经精通Lisp,一般很难想像你是从何入手的。首先,你肯定是幸运的,因为有某位懂得Lisp如何工作的人教你。我那样说,是因为如果学生把Lisp当作自己的第三或第四门语言来学,就会有太多的东西是通过老师传授给他的。问题在于他们通常不使用Lisp思考。他们知道它的语法,但那对他们来说,犹如一个说英语的人在写日文时,通过使用字典和语法,从英语翻译过去一样。
同样的问题适用于那些真正学过Logo(往往通过反复的试验和犯错)的人。如果你的Logo知识停留在Turtle Graphics,那么在学习C++的过程中可能不会有太大的挣扎,但如果你走得更远,那么你需要专注于函数、重复(repetition)以及判断(decision)等基础概念。它们的语义对于所有计算机语言都是通用的,但语法却大不相同。
正如那些将Lisp或Logo作为第三或第四语言的人苦苦挣扎一样(因为他们会不断地设法将过程式语言的结构强加到正在学习的新语言之上),那些走其他道路的人也不得不放弃他们已经发展起来的关于表处理语言(list processing language)和命令式语言(imperative language)的思维方式。至少你需要暂时将这些思维方式“挂起”,直到你掌握了C++的基础为止。
针对面向对象程序员的基础C++
有时人们将C++描述为一门面向对象编程语言(object-oriented programming language,OOPL)。其实不然。它是一门你可以用来进行某些形式的面向对象编程(object-oriented programming,OOP)的语言。如果你希望进行纯粹的OOP,可以尝试一种像Smalltalk这样的语言。然而,如果你来自这么一种背景,请千万小心,因为OO的C++形式显著不同于你可能学过的OO形式。存在的很多的相似性会麻痹你,使你处于一种安全的意识,同时存在很多的差异使得这种所谓的安全意识其实只是一个错觉。
默认情况下,方法(噢,在C++中我们称其为成员函数)是静态绑定的。就是说,在编译期而不是在执行期选择实现代码。C++提供一种机制以将绑定延迟到运行期(即所谓的动态绑定),但程序员需要明确地表达那就是他们所需要的东西。
在C++中,并非每样东西都是通常意义上OOPL对象。在C++中谈论对象时,我们所传达的意思并不精确等同于Smalltalk程序员通过对象所传达的意思。也就是说,概念接近但并非完全相同。
针对每一位程序员的基础C++
你学过的可能还有许多其他语言。Fortran、Modula 2或3、Forth和Prolog仅仅是几种或深或(通常)浅地了解一些的语言。倘若你年龄足够大,你可能会熟悉PL/1,若你是一位数学家,你可能使用APL(一种杰出的解释型语言,专供那些思考“其他人几乎不可能掌握的数学问题”的人使用)。你也可能学习过Ada。如果你足够钻研,你还可能学过SNOBOL。我之所以把它放在最后一位,因为它是一门庞大而不流行的语言。然而,我相信Andy Koenig拥有一套库和其他工具,使得SNOBOL机制和惯用法可用于C++之中。
无论你已经知道哪一种或哪几种语言,C++都会为你提供一些新东西,你以前的知识和技能也会对你的C++学习提供一些帮助或障碍。最重要的是,你不要试图把C++仅仅作为编写你已知的语言的另一种方式。如果你因为希望拓宽就业面而学习C++,这尤其重要。倘若接下来你试图在C++的外衣下编写X语言的代码的话,了解足够的C++以通过工作面试并不足够。
优秀的程序员能够熟练地使用多种计算机语言编写代码,拙劣的程序员使用多种计算机语言编写同样恶劣的代码。我希望到你完成C++学习之日,你既是一名更好的程序员,也是一名优秀的C++程序员。...