前言(Preface)
象任何一种人类语言一样,C++提供了一种表达概念的方法。如果这种方法成功的话,那么当问题的规模以及复杂性逐渐变大时,这种介质将比其它方式更有力,而且使用也更简单、更灵活。
不能将C++看作一些特性的简单集合,其中的一些特性单独使用的话是没什么意义的。在设计和编码的时候应该把它们当作一个整体来考虑。要真正理解这一点,需要了解C语言一些问题,以及编程中会出现的一般性问题。这本书就讨论了编程中会遇到的一些问题,以及它们之所以成为问题的原因,然后再阐述C++用以解决这些问题的方法。因此,每一章中所讨论的特性是在用这门语言解决某种特定类型的问题的时候会用到的一组有机关联的特性。我希望通过这种方式逐步的把C++的观念灌输到一个用惯了C语言的人的头脑中,从而最终使得C++成为你的"母语"。
而且在整本书中,我都试图帮助你在大脑中建立这么一个模型,使得你可以了解这门语言背后的所有秘密,这样当你遇到一点疑惑时,可以用这个模型来引导自己找到答案。我将试图将我头脑中对于C++的理解传达给你,这种理解使得我可以"用C++来进行思考"。
第二版有什么不同(What’s new in the second edition)
相对于第一版来说,这本书被彻底重写了一次,它完全遵循最终的C++标准(这个标准与以前的C++版本有些不同),同时也体现出了我从编写本书的第一版以来自己学到的一些东西。第一版中的所有文字都重新进行了检查和修订,有些旧的例子被去掉了,有些例子进行了修改,并且加入了一些新的示例,同时还加入了很多新的练习。对本书的素材进行的重大调整基于下面两个原因,一是帮助人们去理解和使用一些更好用的特性,同时也是因为这些年来对于人们如何来学习C++这个问题我自己有了一些新的体会。这一版中新增了一章,简明扼要的介绍了C语言的基本概念以及C++的基本特性,这使得那些没有C语言背景的人也可以应付本书的其它内容。
因此对于这个问题:"这本书的第二版有什么不同",最简单的回答就是:对第一版中不符合新标准的内容进行了重写,对于在原来的素材中没有明确体现的一些要点在新版中则得到了清晰的体现。
本书的第二卷中都有些什么内容(What’s in Volume 2 of this book)
C++标准中新增加了几个很重要的库,譬如标准C++库中的string、容器以及算法等,而且其中的模板也更加复杂了一些。这些以及其它一些高级主题,如多重继承、异常处理、设计模式以及如何建立和调试稳定的系统等都被归入到本书的第二卷中。
如何得到第二卷的英文版(How to get Volume 2)
本书的第二卷也可以从我的网站www.BruceEckel.com上下载。从那里也可以看到第二卷出版的预定日期。网站上还有这两本书的源代码以及其它一些信息。
背景知识(Prerequisites)
在本书的第一版中,我做了一个假定,就是认为读者已经了解了这本书所需要的关于C语言的基本知识。我最初的重心放在了如何阐明我认为最为困难的地方:C++语言本身。而在这一版中,我加入了一章,用于对C做一个简明扼要的介绍,但我仍然假定你具备一定的编程经验。另外,就象在读小说时,常常可以根据上下文猜测出一些新词的涵义一样,在本书的其它部分中,你也可以这样来学到很多关于C语言的东西。
学习C++(Learning C++)
可能很多读者跟我当年开始学习C++时的情况一样,对于编程非常严肃而且狂热。而我可能更糟糕一些,因为我一直在做硬件级的嵌入式编程,那里C语言通常都被看作高级语言,在对二进制的位进行操作时效率不尽人意。后来我发现自己甚至不是一个很好的C程序员,对于数据结构、malloc()、free()、setjmp()、longjmp()以及其它一些"复杂"的概念显得很无知,当谈论到这些东西的时候总是很羞怯的逃避,而不是试图去掌握这些新东西。
当我开始学习C++时,唯一一本象样的书就是Bjarne Stroustrup自称为"专家指南"(expert guide)的《The C++ Programming Language》,所以我需要自己来弄清楚一些基本概念。这成就了我的第一本书:《Using C++》,它基本上是我自己的一些经验。当时的想法是将读者同时带入C和C++的世界。这本书的两个版本(《Using C++》和《C++ Inside & Out》)都得到了读者的热烈回应。
在出《Using C++》这本书的同时,我开始教授这门语言。教授C++(后来也教Java)成为我的职业,从1989年开始,在世界各地的教学活动中,经常会发现听众昏昏欲睡、茫然或者感到困惑。当我对一些小组进行室内培训时,在进行练习的过程中,又发现了一些东西。尽管学员们会有会心的微笑,但实际上他们对许多问题并没有弄得很清楚。通过在软件开发研讨会中创建和主持C++和Java论坛的多年经历,我终于发现了问题所在,那就是我以及其它老师总是试图尽快的向学员灌输过多的主题。最后,由于学员中存在不同的层次,也因为我采用的这种教授方式,最终会使一些人中途退出。也许要求有点过分,但因为我也是传统教学方法的抵制者之一(我相信对于大多数人这样的抵制是因为这种方法让人厌倦),我希望能使得每一个人都能突飞猛进。
在一段时间里,我创建了一系列不同的快速教学方案,不断的从试验和反馈这个迭代过程(在C++的程序设计中也常常会用到迭代)中进行学习。最后根据所有这些从教学中积累出来的经验,我制订了一套课程方案。它把整个学习过程分成一个个离散的、易于消化吸收的子过程,而教学形式是定期举行的研讨会(这是理想的学习方式),每一期研讨会后都会进行一些练习。可以从www.BruceEckel.com中得到我的公开研讨会中发布的资料。
本书的第一版是在两年的教学过程中编写出来的,而本书上的例子都是在不同的研讨会上以多种不同的形式进行过实际的测试。每次研讨会上得到的反馈信息都帮助我来对书中的素材进行修改和调整,直到我认为它已经真正适合作为一种教材为止。但它并不是研讨会中所分发的印刷品,我对其中的素材进行了精心的选择和重新编排,从而使你可以从一个主题很顺利的过渡到另一个主题。更重要的是这本书也是为那些为新的编程语言而苦苦奋斗的孤独的求学者设计的,自学者可以藉此步入C++的世界。
目标(Goals)
本书的目的是:
1. 逐步对素材进行展示,从而使读者可以比较容易的消化吸收每个概念,再进行下面的学习。
尽可能使用简单短小的示例。这样的话通常就不会用到"现实世界"中的问题,也许用这门语言所能解决的现实问题的范围以及规模本身会给读者留下深刻的印象,但我发现对于初学者,当他们能够理解示例中的每个细节时所获得的快乐要多一些。同时,对于教学用的示例代码的数量,本身也受到了一些限制。由于这个原因,经常会有人批评我总是使用一些"小儿科示例",我承认这一点,因为它可以更有教学意义。
合理安排教学顺序,避免在前面的章节中出现还没有讲到的特性。当然,有时可能无法做到这一点,这时会对这个特性先做一个简要的描述。
只把我认为理解这门语言所需要的知识教给读者,并不是要将我知道的一切都展示出来。我相信"信息重要层次",有一些东西95%的程序员都不会用到,放在这里只会将他们弄糊涂,并且使他们觉得这门语言是如此之复杂。举个例子,就象C语言中的运算符优先级,如果能记住所有的运算符优先级(我从来做不到),就可以写出很精巧的代码。但这样的话,代码的阅读者和维护者,乃至你自己会被这种内在的优先级关系弄糊涂。因此最好是不要管什么优先级,当有那么一点不明确的时候就毫不犹豫的用括号。也可以这种观念来对待C++中的某些东西。我认为这些东西对于编译器的编写者,而不是程序员更为重要。
每一部分的内容都比较集中,这样使得讲教授的时间,以及练习之间的间隔时间都比较合理。这不仅使得学员在这一系列的学习过程中保持积极活跃的态度,也给了读者更多的成就感。
给读者打下一个坚实的基础,使他们真正理解这些内容,从而可以进行更高层次的学习(包括进行第二卷的学习)。
我尽量不使用任何特定版本的C++,因为对于学习语言来说,我认为语言本身比某种特定的实现要重要。而且每个版本的C++所附带的文档中对它的特别之处肯定都已经做了详尽的描述。
章节介绍(Chapters)
C++是在已有语法的基础上实现了一些新特性,因此它不是纯种的面向对象编程语言。很多人在学习这门语言的过程中都走了一些弯路,这使我们逐步意识到了一个程序员在学习C++语言时所经历的一些过程。对于一个被面向过程编程熏陶了多年的大脑来说,这个过程是非常自然的。我试图来探索并遵循这个过程,并通过提出并回答某些问题来加速这个过程,这些问题有些是我曾经遇到的,有些是在我教授这门语言的时候学员提出来的。
在设计这门课程时,我始终保持一种观念:使C++的学习过程保持自然流畅。听众的反馈使我认识到哪些部分比较难,需要更多的阐述。以前在某些部分我有一种强烈的愿望,想一次讲述很多个特性,我慢慢认识到,如果这样的话,对所有这些特性都必须解释清楚,学员们就会将多个不同特性中的问题相互牵扯,弄得糊里糊涂。后来,我花了大量的功夫进行调整,从而一次只引入尽可能少的特性,最理想的是,一章之中只包含一个主要的概念。
我的目标是每一章只介绍一个概念,或者是很少的几个相关概念,不牵扯依赖其它的特性。这样,在现有知识的基础上可以对所学的片断融会贯通,然后再继续往下进行。为了能做到这一点,我在适当的地方保留了一些C语言的特性。这样,就不至于在介绍C++特性之前就用到它们,从而把读者弄糊涂。这样,你的C++之旅将是非常平稳的,逐步的吸收消化这些新特性。
下面是对本书中各个章节的简要介绍:
Chapter 1: 关于对象(Introduction to Objects)。当项目变得过大而且过于复杂,以致难以维护时,就导致了"软件危机",就象程序员们说的:"我们难以完成这个项目,即使我们完成了的话,它的成本也太昂贵了!"这种危机引起了多种反应,本章中所讨论的就是如何用面向对象的编程方法(OOP)来消除软件危机。这一章讲述了面向对象编程的一些基本概念和特性,同时也介绍了分析和设计过程。另外,也将谈到采用这门语言的好处,以及如何转入C++世界的一些建议。
Chapter 2: 构造和使用对象(Making and Using Objects)。这一章讲述了用库和编译器建立程序的过程。这里引入了本书中第一个C++程序示例,并且展示了程序是如何被构建和编译的。然后还介绍了标准C++库中一些基本对象的库。通过学习这一章,你会很好的理解使用已有对象库对于编写C++程序的巨大意义。
Chapter 3: C++中的C(The C in C++)。这一章对C++中用到的C的特性做了一个简明扼要的阐述,同时也引入了一些只在C++中存在的基本特性。还介绍了在软件开发中一般都会用到的一个工具"make"。这一章假定你有较好的面向过程语言(如Pascal、C甚至是Basic,只要你用它写过足够多的代码,尤其是函数)编程的基础。如果觉得这一章还有些难度,那么可以先看一下《Thinking in C》这本书(从www.BruceEckel.com可以下载)。
Chapter 4: 数据抽象(Data Abstraction)。C++中的大多数特性都是围绕着创建新数据类型这个主题的。这不仅能更好的组织代码,同时也是面向对象编程中其它强力特性的基石。你会发现这样做十分简单,只是将函数放到结构内部就行了,你也会看到其中的细节,同时会认识到它能给你带来什么样的代码。最后还将学到如何最合理的将代码分别组织到头文件和实现文件中去。
Chapter 5: 实现隐藏(Hiding the Implementation)。你可以使得一个新类型中的某些函数和数据对于该类型的用户是不可见的,只要将它们声明为private的就行了。这表示你可以将实现部分从用户能够看到的接口中分离开来,从而可以很容易的修改实现部分而不至于影响客户的代码。这里引入了关键字class来描述新的数据类型。到这里,"object"这个单词的意义就不再那么神秘了(它只不过是一种特别的变量)。
Chapter 6: 初始化和清除(Initialization and Cleanup)。C中最常见的错误往往是源于未被初始化的变量。C++中的构造函数可以保证新数据类型的变量总能得到正确的初始化。如果你的对象还需要某种清除操作的话,也可以用C++的析构函数来保证。
Chapter 7: 函数重载和缺省参数(Function Overloading and Default Arguments)。 C++可以用来帮助你建立大型而且复杂的项目。当这种时候,可能会出现使用的多个库中函数名称重复的问题,甚至在一个库中也可能将同一个名字用于不同的意义。C++使用函数重载(function overloading)来解决这个问题,只要函数的参数列表不同,那么两个函数就可以使用同一个名字。缺省参数(Default argument)自动的为某些参数提供缺省值,从而可以用不同的方式来调用同一个函数。
Chapter 8: 常量(Constants)。这一章讲述了关键字const和volatile,它们在C++中有一些附加的含义,尤其是用于类的时候。你会学到当将const应用到一个指针定义上时的含义到底是什么。同时还阐述了在类的内部和外部使用关键字const的不同意义,并且还教你如何在一个类中创建编译期常量。
Chapter 9: 内联函数(Inline Functions)。预处理器中的宏消除了函数调用带来的额外开销,但它同时也屏蔽掉了C++的类型检查。内联函数则使你兼得宏与真正的函数调用二者的优点。这一章讲述了内联函数的内部实现机制以及它的用法。
Chapter 10: 名字控制(Name Control)。创建名字是编程的一个最为基本的行为,当项目逐步变大,名字的数目增多,这时就可能会导致名字冲突。C++大大加强了对名字的管理,使你可以对名字的创建、名字的可见性、名字在内存中对应的位置以及名字的链接属性(详见第10章)进行控制。本章展示了在C++中如何使用两种技术来对名字进行控制。首先,用关键字static来控制名字的可见性和链接属性,同时也讲述了这个关键字在类内部时的特殊含义。在全局范围内对名字进行控制的一个更有用的技术是C++的名字空间(namespace)特性,它使得你可以将全局的名字空间分成不同的区域。
Chapter 11: 引用和复制构造函数(References and the Copy-Constructor)。 C++中指针的用法与在C中的用法基本相似,但在C++中对指针进行了更为严格的类型检查。C++还提供了另一种操作地址的方法:即引用,它使得你可以用普通的标识符来对地址进行操作。你还会学到复制构造函数,它能控制以传值方式将对象传入或者传出函数的实现方式。最后还将讲述关于指向成员的指针。
Chapter 12: 运算符重载(Operator Overloading)。这个特性有时被成为"语法糖"(syntactic sugar),通过把运算符当作是函数调用一样使用,从而使你定义的新类型的用法变得"甜蜜"起来。通过这一章,可以了解到其实运算符重载只不过是函数调用的另一种形式而已,你将学会如何来设计自己的运算符来处理一些容易混淆的参数和返回值,并且知道什么时候将运算符当作一个成员,什么时候把它当作友元(freind)。
Chapter 13: 动态对象创建(Dynamic Object Creation)。一个空中交通管理系统中将要管理到多少架飞机?一个CAD系统中会用到多少个形状?在这种一般的编程问题中,无法预先知道程序运行期间会用到的对象类型、数量和生存期。这一章中,你会看到C++是如何通过用关键字new和delete来安全的在堆中创建对象从而优雅的解决这类问题的。还将学会如何用不同的方法来重载new和delete,从而控制内存分配和释放的方式。
Chapter 14: 继承和组合(Inheritance and Composition)。数据抽象使你可以从无到有的创建出新的类型,而用组合和继承,则可以利用已有的类型来创建新类型。使用组合,就可以用已有类型装配出新的类型,而用继承,就创建了一个已有类型的特定版本。本章将学到继承和组合的语法、如何重定义函数、以及在继承和组合时构造函数和析构函数的重要性。
Chapter 15: 多态性和虚函数(Polymorphism and virtual Functions)。完全靠自己摸索的话,可能得化上九个月来探索和理解这个面向对象编程的核心思想。而通过一些小的简单的例子,则很容易的学会如何用继承来创建一个类族,并通过这个类族的公共基类来处理这个类族中所有类的对象。关键字virtual使得你可以用一般的方法来对待这个族中的所有对象,这就是说你的代码并不依赖于特定的类型信息。这样你的程序就具有可扩展性,从而使程序的构建和维护更加简易,同时也更为经济。
Chapter 16: 模板的介绍(Introduction to Templates)。
继承和组合使你可以复用对象的代码,但这并不能完全满足复用的需求。模板通过使类型参数化来达到代码复用的目的。容器类库(container class library)就是在模板这种技术的基础上建立起来的,容器类是面向对象编程中进行快速可靠开发的一种非常重要的工具(标准C++提供了一个很重要的容器类库)。这一章为你继续深入学习这个主题打下了很好的基础。
本书的第二卷中讲述了一些其它的更为高级的主题,这本书可以在www.BruceEckel.com处进行下载。
练习(Exercises)
我发现在讲座中练习特别有助于学生对概念的真正理解,因此基本上每一章后面都设计了一些练习。这一版中的练习数量大大多于第一版。
许多练习都相当简单,可以在课堂或者实验室里有老师辅导的情况下完成,这样可以确保学员真正理解吸收所学的东西。有一些练习有一定的难度,从而使学有余力的学生有用武之地。这些练习一般都被设计成可以在较短的时间内完成,练习的目的主要是检验并且帮助你消化这些知识,而不是给你出难题(可以自己去找一些难题——而通常是它们会来找你)。
练习解答(Exercise solutions)
关于习题的解答可以从www.BruceEckel.com处进行下载,不过要收取一定费用。(译者注:你可能注意到了本书中所有的练习并没有翻译成中文,是有意这样的。英语是一门更加通用的语言,在计算机世界里尤其如此。一本优秀的英文书籍辗转流落到中国再到翻译出版,往往要几年时间,不禁让人扼腕叹息。具有良好的英语阅读能力能帮助你更快的接触到先进的技术和思想。因此译者希望能通过这些练习帮助你更多的了解一些相关的英文术语。稍后会专门出版一本中文版的关于第一卷和第二卷的习题及解答的书)。
源代码(Source code)
本书中的源代码是可以免费使用的,可以从www.BruceEckel.com处进行下载。但未经许可,不得在任何出版物中使用这些代码。
这些代码被打包成zip文件,下载后用适当的工具解压即可。在解压生成的目录中可以看到下面的版权声明:
//:! :Copyright.txt
Copyright (c) 2000, Bruce Eckel
Source code file from the book "Thinking in C++"
All rights reserved EXCEPT as allowed by the
following statements: You can freely use this file
for your own work (personal or commercial),
including modifications and distribution in
executable form only. Permission is granted to use
this file in classroom situations, including its
use in presentation materials, as long as the book
"Thinking in C++" is cited as the source.
Except in classroom situations, you cannot copy
and distribute this code; instead, the sole
distribution point is http://www.BruceEckel.com
(and official mirror sites) where it is
available for free. You cannot remove this
copyright and notice. You cannot distribute
modified versions of the source code in this
package. You cannot use this file in printed
media without the express permission of the
author. Bruce Eckel makes no representation about
the suitability of this software for any purpose.
It is provided "as is" without express or implied
warranty of any kind, including any implied
warranty of merchantability, fitness for a
particular purpose, or non-infringement. The entire
risk as to the quality and performance of the
software is with you. Bruce Eckel and the
publisher shall not be liable for any damages
suffered by you or any third party as a result of
using or distributing this software. In no event
will Bruce Eckel or the publisher be liable for
any lost revenue, profit, or data, or for direct,
indirect, special, consequential, incidental, or
punitive damages, however caused and regardless of
the theory of liability, arising out of the use of
or inability to use software, even if Bruce Eckel
and the publisher have been advised of the
possibility of such damages. Should the software
prove defective, you assume the cost of all
necessary servicing, repair, or correction. If you
think you've found an error, please submit the
correction using the form you will find at
www.BruceEckel.com. (Please use the same
form for non-code errors found in the book.)
///:~
只要包含这些声明,就可以在你的项目以及课堂上使用本书的代码。
对语言的支持(Language support)
你的编译器可能不支持本书中所讨论的所有特性,尤其是如果你没有得到该编译器的最新版本时。实现象C++这样一门语言是一个长期的任务,不可能期望所有的特性都能一下子被开发出来。如果试图编译本书的例子时得到大量的错误提示,那么没有必要对代码进行调试,这很可能是你的编译器还不支持代码中用到的某些特性。
书中的错误(Errors)
无论一个作者使用多少中方法来检查书中的错误,难免还是会漏掉一些。如果你确信发现了书中的某个错误,请与作者联系,不胜感谢。