像计算机科学家一样思考Java(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-18 02:45:37

点击下载

作者:[美]Allen B. Downey 著

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

像计算机科学家一样思考Java

像计算机科学家一样思考Java试读:

前言

我们从别人的发明中享受了很大益处,我们也应该乐于以我们的任何一种发明为别人服务,并且这种事我们应该自愿地和慷慨地去做。——本杰明·富兰克林,引用自Edmund S. Morgan所著的《本杰明·富兰克林传》

我为什么写这本书

1999 年,当我还在科尔比学院教书的时候,我便开始写这本书了。现在,这已经是第5个版本了。那时,我正使用Java程序设计语言来教授计算机科学导论课程,但始终没有找到一本合适的教材。一方面,这些书的内容太多,让学生去阅读一本800页的技术书籍显然不可能,即便是我硬叫他们去读也不可能,更何况我并不想他们这么做。另外,书中多数内容的针对性都很强,比如 Java 语言的细节和 Java类库等内容,而这些类库在学期结束时很有可能就已经过时了。这样的书籍显然不是我需要的。

另一方面,我发现一开始就讲面向对象编程并不合适。很多在其他方面表现得很好的学生,一旦接触到对象就一头雾水了。于是我开始写这本书,每天写一章,一共写了13天,第14天编辑,然后是复印和装订。上课第一天我便把书分发给学生们,并叫他们每周阅读一章,这样一来,他们阅读的速度比我书写的速度慢了7倍。

背后的哲学

以下是我写这本书的一些想法:

• 词汇很重要。学生应该有能力去讨论程序并且能够听懂我所讲的话。我尝试着引入尽可能少的术语,并在第一次使用这些术语时精心地给出定义,然后在每章的最后附上术语表。在我的课程中,我会在小测验和考试中包含一些和词汇相关的题目,然后要求学生使用恰当的术语进行回答。

• 要编写程序。学生应该理解算法,知晓程序设计语言本身,并能够调试程序。太多的书籍都将程序调试内容给忽略掉了。本书最后的附录D讲述程序调试的内容,附录C讨论程序开发内容(可以帮你避免调试)。我建议学生们尽早地阅读这些内容,并且时常回过头来复习。

• 有些概念需要时间来消化。对于一些较难的内容,本书将反复讲到,这样可以给学生更多的机会去复习和巩固。如果第一次没有学会,可以在后面赶上。

• 我试图尽量少地使用 Java 语言来教会学生获得尽量多的编程能力。本书的目的在于教学生如何编写程序和一些计算机科学导论方面的概念,而不是 Java语言。因此,我去除了一些Java语言特性方面的内容,比如switch语句便没有必要了,多数类库也不在本书的教授范围之内,特别是一直在变并很可能被替换掉的AWT。

这种最小化的方式有它的优点。除了章后练习,每章大概10页左右。在我的课程中,我要求学生提前阅读要讲的章节,他们都愿意这么做并且理解得很好。这样一来,我们便有时间来讨论一些更抽象的内容、做一些课内练习和学习本书之外的内容。

但是,这种最小化方式也有缺点。从本质上讲,这种方式没有多少有趣的地方。书中的多数例子只是在展现基本的语言特性,并且很多练习都包含了字符串操作和数学相关的知识。我认为其中的一些内容是有趣的,但是还有很多能够激发学生兴趣的内容,比如图形、声音和网络应用只是一带而过。

问题在于,很多令人兴奋的内容都包含大量的细节,而其中的概念并不多。从教学上讲,这样浪费了太多的努力但又达不到教学目的。因此,在取悦学生和富含知识之间便出现了一个折中。我让老师们自己去解决这种平衡。作为帮助,本书的附录中包含图形、键盘输入和文件输入等内容。

面向对象编程

有些书一上来便介绍对象,另一些书先通过面向过程的编程方式进行引导,再逐渐讲授面向对象编程方式。本书采用后一种方式。

Java中许多面向对象特性都是由先前语言出现的问题来驱动的,并且它的实现也受到了这些语言发展历史的影响。在学生不了解他们需要解决的问题的情况下,有些特性是很难解释的。推迟讲面向对象编程并不是我的意图所在,相反,我会尽快地涉及到这些内容,只是我每次只讲一个概念,并且尽可能清晰,以使学生可以相对独立地练习每一个知识点,然后再加入后续的概念。但是我也承认,达到这样的目的确实需要时间。

计算机科学预科考试

当科尔比学院宣布预科考试将转向Java,我顺其自然地将本书更换成Java版本。当看到预科的考试大纲时,我发现大纲中的内容完全就是我书中所选内容的一个子集。

2003年1月,我正在写本书的第4个版本,该版有如下变化:

• 增加了一些章节以覆盖更多的预科大纲。

• 改进了关于调试和程序开发内容的附录。

• 将我在上课时用到的练习、小测验和考试的题目放在了相关章节的末尾。同时,针对预科考试增加了一些题目。

最终,在2011年的8月,我开始了本书第五版的写作,增加了预科考试中包含的GridWorld案例研究。

免费!

从一开始,本书便允许读者进行复制、分发和修改。读者可以下载到本书不同格式,并且既可以在计算机上阅读,也可以进行打印。老师可以免费地进行多份复印。任何人均可以按照自己需求对本书进行定制性的修改。

有人将本书翻译成其他的计算机语言(包括Python 和Eiffel),也有翻译成其他的自然语言(包括西班牙语、法语和德语)。这些翻译版本中的许多也是免费的。

受开源软件的驱动,我也采用了“早发布,勤更新”的方式。我尽量减少错误,但同时我也需要读者的帮助。

本书的反响是很好的。我几乎每天都收到读者的反馈信息,他们甚至不嫌麻烦将“bug报告”发送给我。通常,我都会在几分钟内更改书中的错误,然后发布更新。我将本书看做是一份正在进行中的工作,一旦有时间或者有读者发回反馈,我都将一点一点地进行改进。

关于书名

本书的书名给了我不少的痛苦。不是所有人都知道,本书的题目在很大程度上只是个玩笑。阅读本书很可能不会让你像一个计算机科学家那样思考,那需要时间、经验,并且很可能还需要更多的课程。

但本书的书名的真实含义在于:它不是关于 Java 的,并且不是完全关于编程的。如果成功的话,那么本书是关于思考方式的。计算机科学家自有一套解决问题的方法,而且这种方法是独特的、通用的和强大的。我希望本书能引领你去感知这样的方法,继而在某种程度上,你会发现自己确实是在像计算机科学家一样思考。

贡献者名单

当我开始写免费图书时,我并没有留意贡献者名单。当 Jeff Elkner建议我之后,我才发现省略掉这份名单是多么的尴尬。这份贡献者名单是从第四版开始的,故许多对先前版本提出建议和改正的贡献者都被省略掉了。如果你有另外的建议,请发送邮件到:feedback@greenteapress.com。

• Ellen Hildreth将本书用在威尔斯利学院的数据结构课程中,并对本书做了一系列的更正,另外还提出了一些很好的建议。

• Tania Passfield曾指出,在第 4章的术语表中,有些术语在该章内容中已经不存在了。

•Elizabeth Wiethoff发现了我对于的解释是错误的,她目前正在写本书的Ruby版本。

• Matt Crawford发给我了一份满是更正的补丁文件。

• Chi-Yu Li指出了一个排版错误和一个代码例子错误。

• Doan Thanh Nam更正了第 3章中的一个例子。

• Stijn Debrouwere发现了一个数学排版错误。

• Muhammad Saied将本书翻译成了阿拉伯文,并发现了多处错误。

• Marius Margowski发现了一个代码例子中的一处不一致。

• Guy Driesen发现了多个排版错误。

最后,我想感谢Chris Mayfield对本书最新版的巨大贡献。他仔细审阅了本书并对书中一百多次错误做了更正。最新版中加入了一些新的变化,比如嵌入式超文本链接,交叉引用,练习题版式的一致性,代码例子中Java语法高亮等。第1章 程序之道

本书的目的在于教你怎样像一个计算机科学家那样思考。我喜欢计算机科学家思考问题的方式,因为他们能将数学、工程学和自然科学中的最好的特性组合在一起。计算机科学家像数学家一样使用规范的语言来表达思想(特别是计算);像工程师一样进行设计并将不同的组件装配成系统,然后在不同的实现方案中进行权衡;又像科学家一样观察复杂的系统,形成假设,再测试预言。

计算机科学家最重要的技能在于问题解决(problem-solving)技能。他们能够对问题进行简明陈述,创造性地给出解决方案,并且将解决方案清楚准确地表达出来。学习编程的过程便是练习解决问题能力的好机会,这也是本章的标题叫做“程序之道”的原因所在。

一方面,你将从本书中学习编写程序,这本身便是一项技能。另一方面,你会将编程当做一种为了实现一定目的的工具,当我们一路走下去,这样的目的也将变得清晰起来。1.1 什么是编程语言

你即将学习的语言是Java,这是一种相对较新的语言(Sun公司于1995年5月发布了 Java的第一个版本)。Java是一种高级语言(high-level language),你可能听过的其他高级语言还有Python、C、C++和Perl。

有了“高级语言”,还有低级语言(low-level language),有时候也叫做机器语言或者汇编语言。粗略地说,计算机只能够执行由低级语言编写的程序。因此,由高级语言编写的程序必须先被翻译成低级语言才能够运行。这样的翻译过程是需要时间的,这也是高级语言的一个小缺点。

但是,高级语言的优点却太多了。首先,使用高级语言进行编程容易得多。花的时间更少,代码更短,更容易阅读和修改。其次,高级语言具有可移植性(portable),这意味着高级语言可以在几乎不修改的情况下运行于多种计算机平台。相比之下,低级语言只能在一种计算机上运行,如果要在另一种计算机上运行,则需要重新编码。

鉴于高级语言的这些优点,几乎所有的程序都是用高级语言编写的。低级语言只适用于少量特殊的情况。

对由高级语言编写的程序进行翻译有两种方式:解释( interpreting )和编译(compiling)。完成解释任务的程序叫做解释器,它读入由高级语言编写的程序,并且按照高级语言的指令执行程序。实际上,解释器按行对程序进行翻译,然后执行命令。

完成编译任务的程序叫做编译器,它在运行任何一条指令之前先读入由高级语言编写的程序并一次性对该程序进行编译。通常,编译过程是一个单独的步骤,程序的运行则在编译过程之后。在这种情况下,高级语言称为源代码(source code),经编译器生成的程序称为目标代码(object code)或者可执行程序(executable)。

Java 语言编写的程序既可以被编译,也能够被解释。和其他语言不同的是,Java的编译过程并不生成机器语言,而是生成字节码(byte code)。字节码和机器语言一样,可以被容易(和快速)地解释,而它又像高级语言一样具有可移植性。因此,在一台机器上编译 Java,在另一台机器上解释运行是可能的。这也是 Java 相比其他高级语言的一个优势,如图1-1所示。图1-1 Java程序的编译和解释

虽然图1-1所示的这个过程看来很复杂,但在大多数程序开发环境下这个过程已经被自动化了。通常你只需要编写源代码,然后点击一个按钮或者键入一个命令便可以完成程序的编译和运行。而另一方面,知道背后在发生些什么事对程序开发者是有价值的,这样就可以方便地找到整个过程中出错的地方。1.2 什么是程序1

程序是说明如何执行计算的一个指令序列。计算可能是数学计算,比如求解方程组或者寻找多项式的根等;但计算也可以是符号型计算,比如查找和替换文档中的文本或者编译一个程序(够奇怪的)。1

注释:此定义并不适用于所有编程语言,其他关于程序的定义,参见http://en.wikipedia.org/wiki/Declarative-programming。

指令也称作语句(statement),它的格式因不同的编程语言而有所不同。但多数语言都包括一些基本的操作。

输入:从键盘、文件或其他设备获取数据。

输出:在屏幕上显示数据,或者向一个文件或其他设备写入数据。

数学:完成基本的数学运算,比如加法和乘法。

测试:检测特定条件并运行适当的语句序列。

重复:重复性地执行某个动作,通常包括一些可变量。

这差不多就是计算机程序了。我们曾使用的每个程序,不管多么复杂,都是由执行这些基本操作的语句组成的。因此,描述程序设计的一种方法便是将大的复杂任务分解成小的子任务,直到这些子任务足够简单,可以被这些基本操作中的一种操作完成为止。1.3 什么是调试

程序中隐藏的未被发现的错误叫做漏洞(bug),跟踪和修改 bug 的过程叫做调试(debugging)。程序中存在三种类型的错误,将它们区分开来有助于更快地定位跟踪错误。1.3.1 语法错误

编译器只能在程序语法正确的情况下才能完成编译任务,否则,编译将失败,程序自然也就不能运行。语法(syntax)是程序的结构和关于该结构的一些规则。

比如,在英语中,一个句子必须以大写的首字母开始,以句点结束。

对于多数读者,少量的语法错误并不是什么大的问题,但编译器就没有这么宽容了。哪怕是很小的语法错误,编译器都会报错并且退出编译过程,你也别想运行程序了。

更糟糕的是,Java中的语法规则比英语还多,而编译器给出的错误信息也不那么有用。在你编程生涯的前几个星期里,你可能会把大量的时间花在语法错误上。随着经验的增加,你的语法错误将会减少,查找错误也会变得更快。1.3.2 运行时错误

第二种错误类型是运行时错误。之所以叫“运行时错误”,是因为这些错误直到程序运行时才会出现。在 Java 中,解释器在执行字节码的过程中发生的错误称为运行时错误。

Java是一种趋向于安全的语言,这意味编译器会捕获大量的错误。因此运行时错误并不多见,特别是简单程序。

在Java中,运行时错误称为异常(exception)。在多数情况下,当异常发生时,通常会有窗口或对话框弹出以显示此时程序正在进行的操作。这些信息对于调试来说是很有用的。1.3.3 逻辑错误和语义错误

第三种错误称为逻辑错误(logicerror)或语义错误(semanticerror)。当程序中存在这样的错误时,编译过程虽然能够完成,也不会生成错误信息,但程序却不能完成正确的工作。具体来说,程序是按照你所写的去执行的,但问题在于你所写的程序并没有表达出你真实的意图。也可以说,此时的程序从语义上讲是错误的。对这种逻辑错误的识别是比较困难的,因为我们必须向后看,根据程序的输出来查找错误。1.3.4 实验性调试

在本课程中,调试是你将要学习掌握的最重要的技能之一。虽然调试有时是件令人沮丧的事情,但同时也是程序开发中最有趣、最有挑战性和最有价值的活动之一。

调试就像侦探工作一样,你根据一堆线索来推断导致结果的原因。

调试同时也像一门实验性科学。一旦你意识到有什么地方出了错误,你便会修改程序再重新运行。如果你的假设成立,那么你就可以对修改后的结果进行预测,进而离成功更近一步。否则,你不得不做出新的假设。就像Sherlock Holmes所说:“当你排除了所有的不可能后所剩下的,不管有多么的不确定,都是事实。”(出自阿瑟·柯南·道尔《四个签名》)

对于有些人来说,编程和调试是一回事情,即编程是逐步调试直到满足要求的过程。在一开始,程序应该就能运行,然后不断地进行修改,这样,在整个开发过程中,你的程序都是可以运行的。

比如,Linux操作系统包含成千上万行代码,然而它起初只是Linus Torvalds为了研究 Intel 80386 芯片而开发的一个小程序。Linus 一开始只是想实现将输出的“AAAA”字符串变为“BBBB”,后来,就这么一个小程序演化成了Linux操作系统。

在后续章节中,我将针对调试和其他编程实践提出更多的建议。1.4 形式语言和自然语言

自然语言(Natural languages)指人类所说的语言,比如英语、西班牙语、法语等。自然语言并不是由人类设计的,它们是自然演化而形成的。

形式语言(Formal languages)是人类为了某种应用上的需要而设计的。比如,数学中的各种记号便是一种形式语言,这种语言能够很好地描述数字和符号之间的关系。化学家也用形式语言来表示化学分子的结构,而更重要的是:

编程语言是一种用来表达计算的形式语言。

形式语言对于语法有严格的规则。比如,“3+3=6”是一个合法的数学表达式,但“3$=”则不是。同样,HO是一个正确的化学名,2而 Zz则不是。2

语法规则有两种:记号规则和结构规则。记号是语言的基本元素,比如单词、数字和化学元素。表达式“3$ =”的问题在于,“$”在数学上不是一个合法的记号(至少据我所知是这样的)。类似地,“Zz”也不合法,因为没有化学元素的简称为“Zz”。2

第二种语法规则表现在表达式的结构上,即怎么编排程序中的记号。表达式“3$ =”在结构上是不合法的,因为等号不能出现在方程的结尾。类似地,在分子表达式中,下标只能出现在元素之后,而不是之前。

当我们在读英语句子或形式语言中的表达式时,我们需要搞清楚这些语句的结构(虽然对于自然语言来说,你是在潜意识里完成这个任务的),这个过程称为解析(parsing)。

虽然形式语言和自然语言有许多相同的特征,比如记号、结构、语法和语义等,但它们也存在不同。

多义性(ambiguity):自然语言充满了多义性,人类可以根据所处环境和其他信息来处理这样的多义性。而形式语言是非歧义的,这意味着不管在什么环境下,一条语句只能表达一种含义。

冗余性(redundancy):为了弥补多义性,同时也为了减少误解,自然语言通常是冗余的,而形式语言则更简洁。

无修饰性(literalness):自然语言中有许多惯用语和隐喻,而形式语言则非常准确。

我们从小就在说自然语言,当转向形式语言时,通常会有一段困难的时间去调整和适应。自然语言和形式语言之间差别在某种程度上就像诗歌和白话文之间的差别一样:

诗歌:词语的发音和意思都是重要的,一首诗作为一个整体营造出一种氛围或情感效果,其中的多义性是常见的,甚至是故意而为的。

白话文:字面上的意思更重要,而结构也承载了更多的语言含义。

程序:计算机程序是非歧义的。我们通过对程序中记号和结构的分析是完全可以理解计算机程序的。

这里有些关于阅读程序(和其他形式语言)的建议。首先,形式语言比自然语言的信息密度更大,由此阅读所花的时间也越长。其次,程序中的结构是很重要的,这样一来,从头到尾、从左向右阅读程序往往不是一个好主意。相反,应该试着在自己的大脑里解析程序,识别记号并且翻译程序中的结构。最后,细节是重要的,像拼写错误和标点错误这样的小错误,虽然在自然语言中不是什么大问题,但是在形式语言中却有很大的影响。1.5 第一个程序

按照传统,我们在学习一门新语言时,所写的第一个程序都是“Hello,world.”程序,该程序的功能是在屏幕上显示“Hello,world.”字符串。Java的“Hello,world.”程序是这样的:

class Hello {

  // main函数,打印简单输出

  public static void main(String[] args) {

   System.out.println("Hello, world.");

 }

}

对于初学者来说,这段程序可能有些难于解释的特征,但它预先给我们展示了一些主题,我们将在之后的内容中涉及到这些主题的细节。

Java程序由类定义(class definition)组成,类的定义具有以下形式:

class CLASSNAME {

  public static void main (String[] args) {

  STATEMENTS

 }

}

这里,CLASSNAME表示程序员自己选择的类名,上例中的类名为Hello。

main 是一个方法(method)。方法表示一个有名字的语句集合。main 函数是特殊的,它是程序执行的入口。当程序运行时,首先执行main函数中的第一条语句,当执行完main函数中最后一条语句时,程序退出。

main 函数可以包含任意多条语句,而上例中只含有一条,这是一条打印输出语句(print statement),功能是在屏幕上显示一条信息。让人疑惑的是,“print”就其本身的意思来说,即可表示“在屏幕上显示信息”,也可表示“向打印机传送数据”。在本书中,我们所说的所有“print”都表示“在屏幕上显示信息”。print 语句以分号(;)结束。

System.out.println是Java类库提供的方法。类库(library)是类定义和方法定义的集合。

Java使用花括号({和})对程序进行分组。在上个例子中,最外层的花括号(第1行和第8行)包含了类的定义,里面的花括号包含了main函数的定义。

第3行以//开始,表明此行是注释(comment)。注释为解释程序功能的文本,编译器将忽略从//到行尾的所有内容。1.6 术语表

问题解决(problem-solving):对问题进行建模,寻求解决方案并表达解决方案的过程。

高级语言(high-level language):便于人类阅读和编写的编程语言,比如 Java。

低级语言(low-level language):便于计算机运行的编程语言。也叫做机器语言或汇编语言。

形式语言(formal language):人类为了特殊用途(比如表达数学理论或计算机程序)而设计的语言。所有的编程语言都是形式语言。

自然语言(natural language):人类所说的所有语言都是自然语言。自然语言经过自然演化而形成。

可移植性(portability):程序能够在多种计算机上运行的能力。

解释(interpret):按行翻译由高级语言编写的程序并执行的过程。

编译(compile):读入由高级语言编写的程序,一次性将高级语言翻译成低级语言,为之后执行做准备。

源代码(source code):由高级语言编写的,并且未经编译的程序。

目标代码(object code):编译器通过编译源代码所生成的输出。

可执行程序(executable):能够运行的目标代码的另一个名称。

字节码(byte code):由编译 Java程序所生成的一种特殊目标代码。字节码与低级语言很相似,但又像高级语言一样是可移植的。

语句(statement):表示一个计算过程的程序的一部分。

打印语句(print statement):用于在屏幕上输出一条语句。

注释(comment):源代码的一部分,包含一些说明性信息,但对程序的运行不产生任何作用。

方法(method):多条语句的一个命名集合。

库(library):类定义和方法定义的一个集合。

漏洞(bug):程序中隐藏的一个错误。

语法(syntax):程序的结构。

语义(semantic):程序所表达的意思。

解析(parse):检查程序并分析语法结构。

语法错误(syntax error):程序中导致解析失败进而编译失败的一个错误。

异常(exception):导致程序运行时失败的错误。也叫运行时错误。

逻辑错误(logic error):导致程序不能按照开发者的预期运行的错误。

调试(debugging):查找并排除以上3种错误的过程。1.7 练习

练习 1.1 计算机科学家有一个烦人的习惯,他们会用一些常用的英语单词来表示其他的意思。比如,在英语中,statement 和 comment 的意思是相同的,但是在程序中就不同了。

在本书中,每个章节的最后都附有术语表,列出这些单词和短语的目的是强调它们在计算机科学中的特殊含义。虽然你可能很熟悉某些单词,但它们的意思却有可能和你先前所知道的完全不一样。

1.在计算机行业中,语句(statement)和注释(comment)的区别是什么?

2.说一个程序是可移植的是什么意思?

3.什么是可执行程序?

练习 1.2 在你做其他事情之前,先配置好环境,使 Java程序能够成功地编译和运行。一些环境提供了与1.5节中示例相类似的样例程序。

1.编写本章中的“Hello,world”程序,编译并运行。

2.在输出“Hello,world!”之后,再添加一行打印语句,比如打印“How are you?”,然后重新编译运行。

3.在程序的任何地方加入一条注释,再编译运行,新加的注释对程序的运行不应该有任何影响。

本练习虽然有些琐碎,却是我们开发其他程序的一个出发点。调试的自信来自于你对自己编程环境的自信。在某些环境下,我们很容易搞不清楚哪个程序正在运行,有时甚至于发生调试的程序和实际运行的程序不一样的情况。打印语句可以方便告诉我们当前运行的是哪个程序。

练习 1.3 在程序中制造尽可能多的错误是一个不错的主意,这样你便可以看到编译器给出的错误信息。有时编译器给出的错误信息很精确,这样你便可以很容易地做出修改。而有时,错误信息是具有误导性的,所以你应该能识别出何时依赖编译器,何时依赖自己。

1.去掉程序中的一个开花括号。

2.去掉程序中的一个闭花括号。

3.将main改为mian。

4.去掉static。

5.去掉public。

6.去掉System。

7.将println改为Println。

8.将pringln改为print。这是很难处理的,因为这是一个逻辑错误,而不是语法错误。System.out.print语句也是合法的,只是它所做的并不是你所期待的。

9.去掉程序中的一个圆括号。然后再多加一个。第2章 变量和类型2.1 更多打印

在main函数中可以加入任意多的语句,比如多打印一行字符:

class Hello {

 // 简单输出

  public static void main(String[] args) {

   System.out.println("Hello, world."); //打印一行字符串

   System.out.println("How are you?"); //打印另一行字符串

 }

}

正如上面的例子所展示的,注释语句既可以加在行尾,也可以单独占一行。

双引号内的短语称为字符串(string),因为它们是由多个字符的序列(串)所组成的。字符串可以包括任意字母、数字、标点和其他特殊字符。

println 为 print line 的简写,在打印完一行后,println 将添加一个特殊字符,叫newline,它会将光标移向下一行。当下一次调用 println 时,新的文本将出现在下一行。

当需要多条打印语句输出在同一行时,应该使用print方法:

class Hello {

 //简单输出

  public static void main(String[] args) {

   System.out.print("Goodbye, ");

   System.out.println("cruel world!");

 }

}

以上代码在同一行中打印出“Goodbye, cruel world!”。在“Goodbye,”和第二个双引号之间有一个空格。该空格会出现在实际输出中,因此它会影响实际输出效果。

双引号之外的空格通常不会影响实际输出效果。比如,对于一下代码:

class Hello {

public static void main(String[] args) {

System.out.print("Goodbye, ");

System.out.println("cruel world!");

}

}

以上两段代码的输出结果相同。行末的换行符也不会影响程序的输出,因此我们也可以这么写:

class Hello { public static void main(String[] args) {

System.out.print("Goodbye, "); System.out.println

("cruel world!");}}

以上代码照样可以编译成功,并且输出结果和前两段代码一样。但是,阅读起来就比较困难了。源程序中的换行和空格可以增加代码的可读性,并且便于定位错误。2.2 变量

操作变量是编程语言的重要功能之一。变量(variable)是用于存储数值(value)的已命名的存储地址。我们可以对数值进行打印、存储和操作,比如先前我们看到的“Hello, World.”、“Goodbye, ”等都是数值。

为了存储数值,需要先创建变量。比如如果想存储字符串,则声明一个新的字符串变量:

String bob;

以上语句叫做声明(declaration)。该语句声明了一个名为bob,类型为String。每个变量都需要一个类型来决定其可以存储什么类型的数值。比如,int 类型可以存储整型数,String类型可以存储字符串。

有些类型名称的首字母是大写的,有些则是小写的。目前我们只需要注意不要写错就行了,后续章节将涉及到这两者的区别。注意,没有像int或string这样的类型,如果我们自己编造一个这样的类型,编译器将对于这样的类型声明将报错。

创建一个整型变量的语法为:

int bob;

其中,bob为变量名,也可以是其他任意名字。通常来说,为变量所起的名字应该表明你打算用该变量做什么,比如,有以下变量声明:

String firstName;

String lastName;

int hour, minute;

通过变量的名字便可以推测出它们应该保存什么样的数值。上例同时演示了如何声明多个相同类型的变量的语法:hour和second都是int类型。2.3 赋值

在创建了变量之后,我们便可以用变量来存储数值了。为变量存储数值是通过赋值语句(assignment statement)来完成的。

bob = "Hello."; // 将“Hello.”赋值给bob

hour = 11;// 将值11赋给hour

minute = 59;// 为minute设值为59

在上面的3条赋值语句中,相应的注释表明了对于赋值语句的3种不同的理解。用词可能有些含糊,但是所表达的意思却很直接:

• 当声明一个变量时,就创建了一个命名的存储地址。

• 当向一个变量赋值时是将数值存放在变量所对应的存储地址中。

通常,表示变量的方式是画一个方框,方框外注明变量的名字,方框里面注明变量存储的数值。这样,以上3条赋值语句便可用图2-1来表示:图2-1 3条赋值语句的示意图

通常的规定是,变量只能存储与其类型相同的数值。比如,不能将String类型数值存在minute中,也不能将int类型数值存在bob中。

但是,这样的规定是容易让人误解的,因为存在多种方式将数值从一种类型转化为另外一种类型,Java有时甚至自动完成类型转换。现在你只需要记住通常的规定就可以了,我们将在后续章节讲到类型转换。

另一个容易让人误解的地方是,有些字符串看起来像整数,但是实际上不是。例如,变量bob包含字符串“123”,由字符1、2、3组成,但这并不等同于数字的123。

bob = "123";// 合法赋值语句

bob = 123;// 不合法赋值语句2.4 打印变量

用println或print可以打印变量的数值:

class Hello {

  public static void main(String[] args) {

   String firstLine;

   firstLine = "Hello, again!";

  System.out.println(firstLine);

 }

}

上面的程序创建了变量 firstLine,然后为其赋值为“Hello again!”,再打印该变量的数值。

当我们说“打印变量”时,事实上是在打印变量的值。如果需要打印变量的名字,那么需要用双引号将变量名括起来。比如:System.out.println("firstLine");

那么我们可以这么写:

String firstLine;

firstLine = "Hello, again!";

System.out.print("The value of firstLine is ");

System.out.println(firstLine);

程序输出为:

The value of firstLine is Hello, again!

不论变量的类型如何,打印变量的值和打印变量的名字在语法上是一样的:

int hour, minute;

hour = 11;

minute = 59;

System.out.print("The current time is ");

System.out.print(hour);

System.out.print(":");

System.out.print(minute);

System.out.println(".");

程序输出为:

The current time is 11:59.

警告:在同一行打印多个变量,通常的方法是在多条 print 语句后加上一条 println语句。在许多环境中,print 语句并不立即打印变量,直到 println 出现为止,此时print语句打印的所有变量都将一次性在同一行中打印出来。如果省略了println,程序可能在结束时都不会执行打印。2.5 关键字

在前面的章节中,我们讲到了可以给变量起任意的名字,但是这并不完全正确。Java保留了一些单词来通知编译器如何解析程序,如果将这些单词用作变量名,编译器就不知道怎么办了。这样的单词叫做关键字(keyword),包括public、class、void、int等。

完整的关键字列表可以从http://download.oracle.com/javase/ tutorial/java/nutsandbolts/_keywords.html下载到。这是Oracle的官方网站,上面还有Java官方文档,本书大量地参考了这些文档。

你并不需要去记这个关键字列表,我建议可以通过多数 Java 开发环境都提供的语法高亮来帮助识别这些关键字。在这些开发环境中编程时,代码将用不同的颜色进行显示。比如,关键字可能用蓝色表示,字符串用红色,其他的代码则为黑色。此时,如果输入的变量名变成了蓝色,则要小心了!此时编译器可能生成一些奇怪的输出。2.6 运算符

运算符(operator)是用来表示计算过程(比如加法计算和乘法计算等)的程序符号。Java中的多数运算符都会按你所期望的那样去工作,因为这些运算符都是常用的数学符号。比如,加法运算符为+,减法运算符为−,乘法运算符为*,除法运算符为/。

表达式可以同时包含变量名和数字,在执行计算之前,变量名将被其存储的值所取代。

加法、减法和乘法运算符和平时的数学计算没什么区别,唯独需要注意的是除法运算符。比如有以下程序:

int hour, minute;

hour = 11;

minute = 59;

System.out.print("Number of minutes since midnight: ");

System.out.println(hour*60 + minute);

System.out.print("Fraction of the hour that has passed: ");

System.out.println(minute/60);

输出为:

Number of minutes since midnight: 719

Fraction of the hour that has passed: 0

第一行输出正常,但第二行则有些奇怪了。minute 的值为 59,59 除以 60 应该为0.98333,而不是 0。问题在于,此时 Java执行的是整数除法(integer division)。

当两个操作数(operand)都为整数时(操作数是运算符的作用对象),结果照样为整数,并且整数除法通常向下取整,即便是整除结果更接近于下一个更大的整数也是如此,就像上例一样。

另一种输出方法是计算百分数而不是分数:

System.out.print("Percentage of the hour that has passed: ");

System.out.println(minute*100/60);

结果为:

Percentage of the hour that has passed: 98

同样,计算结果是向下取整的,但至少是近似于正确值的。要得到更加精确的结果,我们可以用另一种变量类型:浮点数。浮点数可以存储分数值,我们将在下一章中讲到。2.7 运算符优先级

当表达式中出现了多个运算符时,这些运算符的计算顺序取决于运算符的优先级(precedence)。完整的运算符优先级是复杂的,以下是常用优先级规则:

• 乘法和除法优先于加法和减法。比如,2*3−1结果为5,而不是4;2/3−1结果为−1,而不是1(注意:2/3为整数除法,结果为0)。

• 如果运算符具有相同的优先级,那么计算顺序为从左到右。因此,对于表达式minute*100/60,首先执行乘法运算,结果为 5900/60,然后是除法运算,结果为 98。如果运算符执行顺序为从右向左,那么结果将为 59,这样的结果是错误的。

• 任何时候都可以使用圆括号来改变运算符的优先级。用圆括号括起来的表达式会先计算,比如2*(3−1)的结果为4。使用圆括号还可以增加程序的可读性,比

如对于(minute * 100) / 60,虽然结果和minute * 100/ 60一样,但程序显然更容易阅读。2.8 字符串运算符

通常来说,数学运算符不能运用在字符串上,即便是字符串看起来像数字也不能。对于String类型的bob,以下表达式是不合法的:

顺便提一句,从以上的表达式中我们是看不出bob是什么类型的。要知道一个变量的类型,我们只有查看该变量的声明。

但有趣的是,+运算符可以用于字符串,但是和我们通常所理解的加法运算并不一样。对于字符串,+运算符表示字符串拼接(concatenation),即将运算符两侧的字符串首尾拼接在一起。2.9 组合

到目前为止,我们只是相对独立地学习了变量、表达式和语句,并没有将它们合在一起来学习。

编程语言的最有用的特征之一就是可以将较小的程序块组合(compose)在一起。比如,我们已经知道了如何完成乘法运算,也知道如何打印变量,那么我们也可以将这两者组合成一条语句:

System.out.println(17 * 3);

任何包含数字、字符串和变量的表达式,都可以放在同一条打印语句中,比如上文已经出现的一条语句便是如此:

System.out.println(hour*60 + minute);

同样,也可以将任意的表达式放在赋值语句的右侧:

int percentage;

percentage = (minute * 100) / 60;

对于目前来说,这样的功能可能并不起眼,但我们将在后续的章节中学习如何用组合表达式来简洁地完成复杂的运算过程。

警告:赋值语句的左侧应该为变量名,而不是表达式,因为左侧为计算结果存储的位置。表达式并不表示存储位置,其计算结果只是一个值而已,因此,以下语句是不合法的:

minute+1 = hour;2.10 术语表

变量(variable):用于存储数值的已命名的存储地址。所有变量都有类型,类型在创建变量时进行声明。

数值(value):可以保存在变量中的数字或字符串(也可以为其他数据,后续章节会讲到)。所有的数值都有类型。

类型(type):一组数值的集合。变量的类型决定了变量可以存储什么类型的数值。我们所见到的类型有整型(int)、字符串类型(String)。

关键字(keyword):Java中保留的单词,用于编译器解析程序。不能使用像public、class和void这样的关键字作为变量的名字。

声明(declaration):创建新变量的语句,同时指定该变量的类型。

赋值(assignment):为变量指定存储数值的语句。

表达式(expression):变量、运算符和数值的组合,计算结果为一个值。表达式也有类型,并且由其运算符和操作数决定。

运算符(operator):表示某个计算过程(比如加法、乘法和字符串连接操作等)的符号。

操作数(operand):运算符的作用对象即为操作数。

优先级(precedence):计算过程的顺序。

拼接(concatenate):将运算符两侧的字符串首尾拼接在一起。

组合(compositioin):由简单的表达式和语句组成的复合语句,或者是能够完成复杂计算过程的表达式。2.11 练习

练习 2.1 如果你是在课堂上使用这本书,那么本练习一定很有趣。找一个伙伴一起玩玩这个“Stump the Chump”游戏:

首先找一个顺利通过编译并且运行正确的程序。一个玩家离开,另一个玩家向程序中加入一些错误。然后让离开的玩家来查找错误并修复。如果在不编译程序的情况下排除了错误,那么这个玩家获得2分,如果通过编译才找到错误,获得1分。如果玩家没有找到错误,那么另一玩家获得1分。

练习2.2

1.创建一个新程序,名为 Date.java。将上文“Hello,World”程序中的代码拷贝到程序中,并使程序能够编译运行。

2.照着2.4节中的例子,编写一个程序,在程序中创建变量day、date、month和year。day 表示一周中的某一天,date 表示一个月中的某一天。这些变量的类型应该是什么?为这些变量赋值以表示今天的日期。

3.将每个变量在单独一行中打印出来。这个步骤用来确保到目前为止程序是正常工作的。

4.修改程序,将日期打印输出为标准的美国格式:

Saturday, July 16, 2011.

5.再次修改程序,使程序最终输出为:

American format:

Saturday, July 16, 2011

European format:

Saturday 16 July, 2011

本练习的目的在于使用字符串连接来打印不同的类型(int和String)的值,并练习如何一次性向程序中加入若干条语句。

练习2.3

1.创建一个新程序,名为Time.java。从现在开始,我将不会提醒你需要从较小的、可运行的程序入手,但是你还是应该这么做。

2.照着2.6节中的例子,创建变量hour、minute和second,为这些变量赋值以表示当前时间。请使用24小时制,这样要表示下午2点则应该在hour中存储数值14。

3.计算并打印出自从午夜以来所经过的秒数。

4.计算并打印今天还剩下的秒数。

5.计算并打印今天已过时间的百分比。

6.改变hour、minute和second的值,以使它们能够重新表示当前时间(从本练习开始到现在应该过了一段时间了),保证在这些值改变了之后,程序还是正常运行的。

本练习的目的在于训练算数运算,并开始思考诸如时间这样的复合数据体。同时,在用int类型计算百分比时,你可能会碰到一些问题,那么在下一章中,我们将讲到浮点数。

提示

在计算过程中,你可能会想到使用变量来临时地存储数值。这种在计算过程中使用到的,但是并不打印出来的变量有时称为中间变量或临时变量。第3章 无返回值方法3.1 浮点数

在上一章中,当我们处理整数除法的结果为非整数时遇到了一些问题,我们将分数计算变为百分比计算来予以解决,但更通用的方法是使用浮点数。在 Java 中,浮点数类型称为双精度类型(double),全称为“double-precision”。

我们可以像创建其他类型的变量一样来创建浮点数变量并对其赋值:

double pi;

pi = 3.14159;

我们也可以在声明变量的同时为其赋值:

int x = 1;

String empty = "";

double pi = 3.14159;

这样的语法是常见的,对变量进行声明和赋值有时也称为初始化(initialization)。

虽然浮点数很有用,但有时也会造成混淆,原因在于浮点数和整型数之间存在重叠。比如,对于数值1,我们很难分别它到底是整型数还是浮点数或者两者都是。

Java将整型数1和浮点数1.0区分开来对待,即使它们表示相同的数值。两者的类型也是不同的,因此不能在它们之间进行相互赋值。比如,以下语句是不合法的:

int x = 1.1;

原因在于左边的变量是int类型,而右边的数值为double类型。然而,这样的规则是容易被人们忘记的,特别是Java有时会自动完成类型转换,比如:

double y = 1;

从技术上讲,上面的语句应该是不合法的,但在Java中是允许的,此时Java会自动将int类型转换为double类型。这看来是很方便的,但有时也会出现问题,比如:

double y = 1 / 3;

你可能会认为变量的值为0.333333,但实际上却是0.0。原因在于,Java首先进行整数除法,所得的结果为0,在将该结果转换为浮点数,结果为0.0。

解决办法之一是将表达式中的整数换为浮点数:

double y = 1.0 / 3.0;

此时的结算结果为0.333333。

我们之前学习过的数学运算,包括加法、减法、乘法和除法同样可以用于浮点数。但浮点数运算的工作机制和其他类型完全不同。事实上,大多数处理器都有专门的硬件来处理浮点数运算。3.2 双精度型转整型

前面提到,Java在必要的时候自动将int类型转换为double类型,因为在这个过程中信息并不会丢失。相反,如果需要将double类型转换为int类型,则需要进行舍入了。Java并不自动完成这样的转换。

将浮点数转换为整型数最简单的方法便是使用强制类型转换。

强制类型转换的语法为:用圆括号将类型括起来,然后像操作符一样使用,比如:

double pi = 3.14159;

int x = (int) pi;(int)操作符将其后面的数转换为int类型,因此结果x的值为3。

强制类型转换的优先级高于算术运算,在下面的例子中,变量pi首先转换为int类型,因此最终结果为60.0,而不是62。

double pi = 3.14159;

double x = (int) pi * 20.0;

将其他类型转换成整数类型通常向下取整,即使是像 0.99999999 这样的数,转换为整型后应该为0,而不是1。优先级和取整方式有时会使强制类型转换给程序带来很多问题。3.3 数学函数

在数学中,你可能已经接触过像sin、log这样的函数,也知道如何计算表达式的值,比如sin(π/2)和log(1/x)。首先是计算圆括号中的表达式,也被称为函数的参数;然后计算函数本身的值。

这个过程可以反复进行,从而我们可以计算更加复杂的数学表达式,比如 log(1/sin(π/2))。首先我们计算最里层的函数,再计算外层函数。

Java 提供一些常用的数学运算函数,这些函数称为方法(method)。调用数学方法和之前我们学习的print方法语法相似:

double root = Math.sqrt(17.0);

double angle = 1.5;

double height = Math.sin(angle);

上面第一个例子计算 17 的平方根,第二个例子计算角度 1.5 的正弦值。在 Java中,传给 sin 函数或其他三角函数的参数为弧度值。如果需要将角度值转换为弧度值,那么我们需要先除以 360,再乘以 2π。对于圆周率 π, Java提供了Math.PI来表示:

double degrees = 90;

double angle = degrees * 2 * Math.PI / 360.0;

注意,PI全由大写字母组成,Pi、pi或者pie都是错误的。

另一个有用的数学函数是round。round函数将浮点数转换为离其最近的整型数。

int x = Math.round(Math.PI * 20.0);

在上例中,首先执行乘法运算,结果为为 62.8319;再执行 round 函数,计算结果为63。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载