前言

像任何人类语言一样,C++提供了一种表达思想的方法。如果这种表达方法是成功的,那么当问题变得更大和更复杂时,该方法将会明显地表现出比其他方法更容易和更灵活的优点。

不能只把C++看做是语言要素的一个集合,因为有些要素单独使用是没有意义的。如果我们不只是用C++语言编写代码,而是用它思考“设计”问题,那么必须综合使用这些要素。而且,为了以这种方法理解C++,我们必须了解使用C的问题和一般的编程问题。本书讨论的是编程问题、为什么这些编程问题会成为要解决的问题以及用C++解决编程问题所采用的方法。因此,在每一章中所解释的一组语言要素,都建立在C++语言解决某一类特殊问题所用方法的基础之上。以这种方式,我希望一点一点地引导读者,从掌握C开始,直到读者使用C++变成自己的母语思维方式。

我将始终坚持一种观点:读者应当在头脑中建立一个模型,以便从各个方面深入理解这门语言的精髓。如果读者遇到难题,他可以将问题纳入这个模型,推导出答案。我将努力把已经印在我脑海中的见解传授给读者,正是这些见解,使得我能开始“用C++进行思考”。

第1卷第2版中的新内容

本书是第1版的彻底重写,反映了C++标准最终完成所带来的C++的所有改变,也反映了自从第1版写完后我又学习到的内容。我已经检查并重写了第1版中的全部文字,在这个过程中,我删去了一些过时的例子,修改了一些现有的例子,并增加了一些新的例子和新的练习。我对第1版的内容进行了大规模的重新整理和重新编排,以便反映新出现的更好的工具和我对人们如何学习C++的进一步理解。为方便没有C背景知识的读者能阅读本书后面的章节,在第2版增加了一章,简要地介绍C概念和基本的C++特征。

因而,对于“第2版与第1版相比有何不同”这个问题的简要回答是:不同之处不在于版本号是新的,而是进行了重写,有的地方读者甚至无法认出原来的例子和材料。

第2卷的内容是什么

C++标准增加了一些重要的新库,例如String、在标准C++库中的容器和算法,以及模板中的新的复杂性。这些新增的内容和其他更高级的主题被放进本书的第2卷,包括多重继承、异常处理、设计模式和建立和调试稳定系统等内容。

如何得到第2卷

就像当前你手上的这本书一样,《C++编程思想》第2卷完全可以从我的网站www.BruceEckel.com上下载。

预备知识

在本书第1版中,我假定读者已经学习了C,并至少具有自如阅读的水平。我的重点放在简化我认为比较困难的部分:C++语言。第2版增加了一章,快速地介绍C,并在光盘上提供“Thinking in C”的课堂讨论材料,但是即使如此,我仍然假设读者具有一定的程序设计经验。另外,正如读者可以通过读小说而直接地学会许多新词一样,读者也可以从在本书后面的文字中学习有关于C的大量知识。

学习C++

我希望本书的读者有和我进入C++时相同的情况:作为一个C程序员,对于编程持有实在而执着的态度。但糟糕的是,我的背景和经验是在硬件层的嵌入式编程方面。在那里,C常常被看做高层语言,它对于位操作是低效率的。后来我发现,自己甚至不是一个好的C程序员,平时总是掩盖了对malloc()和free()、setjmp()和longjmp()结构以及其他“复杂”概念的无知,当开始触及这些主题时就竭力回避,而不是努力去获取新的知识。

在我开始致力于学习C++时,当时惟一像样的书是Stroustrup夫子自道式的“专家指南”[1],因此我只好靠自己弄清基本概念。这引出了我的第一本关于C++的书[2],这本书基本上就是直接把我头脑中的经验倒出来而写成的。它的构思是作为读者的指南,引导程序员同时进入C和C++。这本书的两个版本[3]都收到了读者的热情反响。

几乎就在《Using C++》出版的同时,我开始讲授这门语言。讲授C++已经变成了我的职业。自1989年以来,在授课时我看到了世界各地听众昏昏欲睡的样子、茫然不知的面容和困惑不解的表情。当我对一些人数不多的人群进行内部培训时,在练习过程中又发现了某些问题。即便那些面带微笑和会心点头的学生,实际上对许多问题也还是糊涂的。通过开创和多年主持“软件开发会议”的C++和Java系列专题,我发现,我和其他讲演者都有一种倾向,即过快地向听众灌输了过多的主题。后来,我做了一些努力,通过区别对待不同层次的听众和提供相关资料的方法,尽量吸引听众。也许这是过分的要求,但是因为我是一个抵触传统教学的人(对于大部分人而言,我相信这种抵触源于厌倦),所以希望我通过努力,使每一个人都能跟得上教学进度。

有一段时间,我编写了大量的教学演示。这样,我结束了通过实验和重复方式进行学习(在设计C++程序的过程中,这也是一项很有用的技术)的阶段。最后,从我多年的教学经验中总结出来的所有内容,形成了一门课程。在课程中,我用一系列分离的、易于理解的步骤并采用实地课堂讨论的形式解决学习中的问题(理想的学习情况),并在每次课后面跟随着练习。

本书的第1版是作为两学年制课程编写的,并且书中的内容已经在许多不同的课堂讨论上通过了多种形式的检验。我从每次课堂讨论上收集反馈意见,不断地修改和调整内容,直到我感觉到它已经成为一本很好的教材为止。但这本书不仅仅是课堂讨论的分发教材,而且我在其中放入了尽可能多的信息,在结构上使得它能引导读者顺利地通过当前主题和进入下一个主题。另外,这本书也适合于自学读者,能帮助他们尽快地掌握这门新的编程语言。

目标

在这本书中,我的目标是:

1)以适当的进度介绍内容。每次将学习向前推进一小步,因此读者能很容易地在继续下一步学习之前消化每个已学过的概念。

2)尽可能使用简短的例子。当然,这有时会妨碍我解决“现实世界”的问题。但是,我发现,当初学者能够掌握例子的每个细节,而不受问题的领域所影响时,他们通常会更有兴趣进行学习。另外,在课堂情况下能达到的接受能力,对代码的长短也有严格的限制。为此,我有时会受到使用“玩具例子”的批评,但是我甘愿承受这一批评,因为这样更有利于取得某些教学法上的效果。

3)仔细安排描述内容的顺序,不让读者看到还没有揭示的内容。当然,这不是总能做到的;如果出现了这种情况,我将会给出简明的介绍性的描述。

4)只把对于理解这门语言比较重要的东西介绍给读者,而不是介绍我知道的所有内容。我相信,不同信息的重要性是不同的。有些内容是95%的程序员不需要知道的,这些东西只会迷惑人们,增加他们对该语言复杂性的感觉。举一个C语言的例子,如果我们记住运算符优先表(我是记不住的),我们就可以写更漂亮的代码。但是,如果一定要这样做,反而会使代码的读者或维护者糊涂。所以可以忘掉优先级,当不清楚时使用括号。我们对于C++中的某些内容也可以采取同样的态度,因为我认为这些内容对于写编译器的人更重要,而对于程序员就不是那么重要。

5)保持每一节的内容充分集中,使得授课时间以及两个练习之间的间隔时间不长。这不仅能使听众保持活跃的思想和在课堂讨论中精力集中,而且会使他们有更大的成就感。

6)帮助读者打下坚实的基础,使得他们能充分地理解面对的问题,从而可以顺利地转向学习更困难的课程和书籍(特别是这本书的第2卷)。

7)我尽力不用任何特定厂商的C++版本,因为对于学习编程语言,我不认为特定实现的细节像语言本身一样重要。大部分厂商的文档只适合于他们自己的特定实现。

各章概要

C++是一个在已有文法上面增加了新的不同特征的语言(因此,它被认为是混合的面向对象的编程语言)。由于很多人走了学习弯路,因此我们已经开始探索C程序员转移到C++语言特征的方法。因为这是过程型训练思想的自然延伸,所以我决定去理解和重复相同的道路,并通过引出和回答一些问题来加速这一进程,这些问题是当我学习该语言时遇到的和听众在听我的课时提出来的。

设计这门课时,我始终注意一件事:精练C++语言的学习过程。听众的反馈意见帮助我认识到哪些部分是很难学习的和需要额外解释的。在这个领域中,我曾经雄心勃勃,一次讲解包括了太多的内容。通过讲解过程,我知道了,如果包括大量新特征,就必须对它们全部作出解释,而且学生也特别容易混淆这些新特征。因此,我努力一次只介绍尽可能少的特征,理想的情况是每章一次只介绍一个主要概念。

本书的目标是只在每一章中讲授一个概念,或只讲授一小组相关的概念,用这种方法,不会依赖于其他的特征。这样,在进入下一章的学习之前,学生可以对自己的当前知识融会贯通。为了实现这个目标,我把一些C特征留到后面的章节去介绍,甚至放在比我希望的还要往后的地方介绍。这样做的好处是读者不会因为看到了许多未解释的C++特征被使用而困惑,因此,对该语言的介绍将是和缓的,并且将反映出读者自己消化这些特征时将会采用的方式。

下面是本书各章内容的简要说明。

第1章 对象导言。当项目对于维护而言变得太大和太复杂时,就产生了“软件危机”。按程序员们的说法,“我们无法完成那些项目,即便能完成,它们也太昂贵了”。这引出了一些问题,在本章中我将讨论这些问题,并且讨论面向对象程序设计(OOP)的思想和如何运用这一思想解决软件危机问题。这一章引导读者了解OOP的基本概念和特征,介绍分析和设计过程。另外,在这一章中,我还将阐述采用这种语言的好处,提出关于如何转入C++语言领域的建议。

第2章 对象的创建与使用。这一章解释用编译器和库建立程序的过程。它介绍了本书中的第一个C++程序,显示程序如何构造和编译,然后介绍标准C++中的可用对象的基本库。在结束这一章时,我们就对如何用流行的对象库编写C++程序有一个深刻的领会。

第3章 C++中的C。这一章详细综述在C++中使用的C的特征和一些只在C++中使用的特征,还介绍在软件开发领域通用的“制作”工具,并且用它建立了本书中的所有例子(本书的源代码在www.BruceEckel.com中可找到,包含了对每章的makefile)。第3章假设读者已经具有某种过程型程序设计语言的坚实基础,例如Pascal和C语言或者甚至某种形式的Basic(只要读者已经用这种语言编写了大量的代码,特别是函数)。

第4章 数据抽象。C++的大部分特征都围绕着创建新数据类型的能力。这不仅可以提供优质代码组织,而且可以为更强大的OOP能力奠定基础。读者将可以看到如何用将函数放入结构内部的简单过程来实现这一思想,并可以看到如何具体地完成这样的过程和创建什么样的代码。读者还能学会组织代码成为头文件和实现文件的最好方法。

第5章 隐藏实现。通过说明结构中的一些数据和函数是private(私有的),可以把它们设置为对于这个新结构类型的用户是不可见的。这意味着能够把下层实现和客户程序员看到的接口隔离开来,这样就容易改变具体实现,而不影响客户代码。另外,C++还引入关键字class作为描述新数据类型的更具吸引力的方法,而单词“对象”的意思并不神秘,它是一种美妙的变量。

第6章 初始化与清除。C语言的最通常的一类错误是由于变量未初始化而引起的。C++的构造函数使得程序员能保证他的新数据类型(即“他的类的对象”)的变量总是能被恰当地初始化。如果他的对象还需要某种方式的清除,他可以保证这个清除动作总是由C++的析构函数来完成。

第7章 函数重载与默认参数。C++可以帮助程序员建立大而复杂的项目。这时,可能会引进使用相同函数名的多个库,还可能会在同一个库中选择具有不同含义的相同的名字。C++采用函数重载使这一问题容易解决。重载允许当参数表不同时重用相同的函数名。默认参数通过自动为某些参数提供默认值,使我们能用不同的方式调用同一个函数。

第8章 常量。本章讨论了const和volatile关键字,它们在C++中有另外的含义,特别是在类的内部。我们将学习对指针定义使用const的含义。本章还说明const的含义在类的内部和外部有何不同,以及如何在类的内部创建编译时常量。

第9章 内联函数。预处理宏省去了函数调用开销,但是也排除了有价值的C++类型检查。内联函数具有预处理宏和实际函数调用的所有好处。这一章深入地研究了内联函数的实现和使用。

第10章 名字控制。在程序设计中,创建名字是基本的活动,而当项目变大时,名字的数目是无法限制的。C++允许在名字创建、可视性、存储代换和连接方面有大量的控制。这一章将说明如何在C++中用两种技术控制名字。第一,用关键字static控制可视性和连接,研究它对于类的特殊含义。另一种在全局范围内更有用的控制名字的技术是C++的namespace(名字空间)特征,它允许把全局名字空间划分为不同的区域。

第11章 引用和拷贝构造函数。C++指针的作用和C指针一样,而且具有更强的C++类型检查的好处。C++还提供了另外的处理地址的方法:继Algol和Pascal之后,C++使用了“引用”,允许当程序员使用平常的符号时由编译器来处理地址操作。读者还会遇到拷贝构造函数,它通过按值传送控制将对象传送给函数或从函数中返回的方式。最后,本章还将解释C++指向成员的指针。

第12章 运算符重载。这个特征有时被称为“语法糖(syntactic suger)”。由于运算符也可以像函数调用那样使用,这使得程序员在运用类型的语法时更加灵活。在这一章中,读者将了解到,运算符重载只是不同类型的函数调用,并且将学会如何写自己的运算符重载,学会处理参数、类型返回以及确定一个运算符是成员还是友元时的一些易混淆的情况。

第13章 动态对象创建。一个空中交通系统将处理多少架飞机?一个CAD系统将需要多少种造形?在一般的程序设计问题中,我们不可能知道程序运行所需要的对象的数量、生命期和类型。在这一章中,我们将学习C++的new和delete如何漂亮地通过在堆上安全地创建对象而解决上述问题。我们还将看到,new和delete如何用不同的方法重载,从而使我们能控制如何分配和释放存储。

第14章 继承和组合。数据抽象允许程序员从零开始创建新的类型。通过组合和继承,程序员可以用已存在的类型创建新的类型。用组合方法,程序员可以以老的类型作为零件组装成新的类型;而用继承方法,程序员可以创建已存在类型的一个更特殊的版本。在这一章中,读者将学习这一文法,学习如何重定义函数,以及理解构造和析构对于继承和组合的重要性。

第15章 多态性和虚函数。单靠读者自己,可能要花九个月的时间才能发现和理解OOP的这一基石。通过一些小而简单的例子,读者可以看到如何通过继承创建一个类型族,看到在这个类型族中如何通过公共基类对这个族中的对象进行操作。关键字virtual使程序员能够按类属处理这个族中的所有对象,这意味着大块代码将不依赖于特殊类型的信息,因此,程序是可扩充的,构造程序和维护代码也变得更容易和更便宜。

第16章 模板介绍。继承和组合允许程序员重用对象代码,但不能解决有关重用需要的所有问题。模板通过为编译器提供了一种在类或函数体中代换类型名的方法,来允许程序员重用源代码。这就支持了容器类库的使用,容器类库是使我们能快速而有效地开发面向对象程序的重要工具(标准C++库包含了一个重要的容器类库)。这一章给出了这个基本主题的详尽阐述。

另一些主题(更高级的课题),可以在本书的第2卷中看到,本书的第2卷可以从网站www.BruceEckel.com下载。

练习

我已经发现,在课堂讨论期间,练习对同学们的完全理解是特别有用的,因此,本书的每章后面都有一组练习。练习题的数量在第1版的基础上有很大的增加。

在这些练习中,很多是比较简单的,可以在课堂内或实验室内用合理的时间完成,老师可以通过观察证实所有的学生正在吸收这些材料。有些练习是为了激发优秀学生的学习兴趣。大量练习被设计成能在短期内求解,目的是为了测试和完善学生所掌握的知识,而不是提出主要的挑战(也许我们将自己找到那些挑战,更可能的是,那些挑战会自动出现在我们面前)。

练习的答案

部分练习题的答案可以在本书的电子文档“Thinking in C++Annotated Solution Guide”中找到,只需支付很少的费用就可以从网站www.BruceEckel.com上获得这个电子文档。

源代码

本书中的源代码是免费软件版权,通过网站www.BruceEckel.com分发。该版权防止未经允许用印刷媒体重印这些代码,但是,在许多其他情况下可以使用这些代码。

这些代码放在一个压缩文件中,可以从任何有zip工具的平台上提取(如果没有安装合适的平台,可以从Internet上找到适合你的平台的某个版本)。

读者可以在自己的项目中和在课堂上使用这些代码,只要遵守代码中的版权声明。

语言标准

在本书中,当谈到遵循ISO C标准时,我一般只是说‘C’。只有当有必要区别标准C和老的、以前版本的C时,我才加以区分。

在写这本书时,C++标准委员会完成了语言的标准化工作。这样,我将用术语“标准C++”来指代这个标准化的语言。如果我简单地谈到C++,读者就应该假设这意味着“标准C++”。

在C++标准委员会的实际名字与标准本身的名字之间有些混淆。委员会的主席Steve Clamage就此作了如下澄清:

有两个C++标准委员会:NCITS(以前的X3)J16委员会和ISO JTC1/SC22/WG14委员会。ANSI授权NCITS建立制订美国国家标准的技术委员会。

1989年J16受委托制订C++美国标准。1991年WG14受委托制订国际标准。J16项目转变为“Type I”(国际)项目,并服从于ISO标准化计划。

这两个委员会在同一时间、同一地点开会,J16的投票作为美国在WG14的票数。WG14委派J16做技术工作,并对J16的技术工作进行表决。

最初,C++标准是作为ISO标准制订的。ANSI后来投票(在J16的建议下)决定采用ISO C++标准作为C++美国标准。

因此,“ISO”是称呼C++标准的正确方式。

语言支持

读者的编译器可能不支持在本书中讨论的所有特征,如果还没有编译器的最新版本就更是如此了。实现像C++这样的语言是艰巨的任务,而且读者可能希望将这些特征划分成一些部分后分别出现,而不是一下子全出现。如果读者试用本书中的某个例子,从编译器中得到了大量的错误,这可能不是代码或编译错误,而可能是他的特定编译器还没有实现例子中的某个特征。

错误

无论一个作者具有多少发现错误的技巧,总会有一些错误漏网,它们常常能被新读者发现。如果读者发现了任何认为是错误的地方,请填写网站www.BruceEckel.com上关于本书的修改表格并在线提交,我将非常感激。

[1]Bjarne Stroustrup, The C++Programming Language, Addison-Wesley,1986(第1版).

[2]Using C++,Osborne/McGraw-Hill 1989.

[3]Using C++and C++Inside&Out, Osborne/McGraw-Hill 1993.