Java语言程序设计基础篇-第10版-第一部分-程序设计基础)

Java程序语言设计(基础篇)-第10版

第一部分 程序设计基础


第1章 计算机、程序和Java概述

1.1 引言

程序设计就是创建(或者开发)软件,软件也称为程序。

简言之,软件包含了指令,告诉计算机(或者计算机设备)做什么。

软件开发人员在称为程序设计语言的强大工具的帮助下创建软件。

每种语言都是为了实现某个特定的目的而发明的,比如,构建在以前语言的长处上,或者为程序员提供一套全新和独特的工具。

关键是学习如何使用程序设计方法来解决问题,这是本书的主旨。



1.2 什么是计算机

概念:

  • 要点提示:计算机是存储和处理数据的电子设备。

计算机的组成:

  • 计算机包括硬件(hardware)和软件(software)两部分。

  • 一般来说,硬件包括计算机种可以看得见的物理部分,而软件提供看不见的指令,这些指令控制硬件并且使得硬件完成特定的任务。

一台计算机是由以下几个主要的硬件组件构成的:
  • 中央处理器(CPU)
  • 内存(主存)
  • 存储设备(磁盘和光盘)
  • 输入设备(鼠标和键盘)
  • 输出设备(显示器和打印机)
  • 通信设备(调制解调器和网卡)
  • 这些组件通过一个称为总线(bus)的子系统连接。

总线:

  • 可以将总线想象成一个连接计算机组件的道路系统,数据和电信号通过总线在计算机的各个部分之间传输。在个人计算机种,总线搭建在主板上,主板是一个连接计算机各个部分的电路板。

1.2.1 中央处理器

概念:

  • 中央处理器(Central Processing Unit,CPU)是计算机的大脑。它从内存中获取指令,然后执行这些指令。

CPU组成:

  • CPU通常由两部分组成:控制单元(control unit)和算数/逻辑单元(arithmetic/logic unit)。

  • 控制单元用于控制和协调其他组件的动作。

  • 算数/逻辑单元用于完成数值运算(加法、减法、乘法、除法)和逻辑运算(比较)。

    现在的CPU都是构建在一块小小的硅半导体芯片上,这块芯片上包含数百万称为晶体管的小电路开关,用于处理信息。

时钟:

  • 每台计算机都有一个内部时钟,该时钟以固定速度发射电子脉冲。这些脉冲用于控制和同步各种操作的步调。时钟速度越快,在给定时间段内执行的指令就越多。时钟速度的单位是赫兹(hertz,Hz),1赫兹相当于每秒1个脉冲。20世纪90年代计算机的时钟速度通常是以兆兹(MHz)来表示的(1MHz就是100万Hz)。随着CPU的速度不断提高,目前计算机的时钟速度通常以千兆兹(GHz)来表述。

    Intel公司最新处理器的运行速度大约是3GHz。

核:

  • 最初被开发出来的CPU只有一个核(core)。核是处理器中实现指令读取和执行的部分。一个多核PCU是一个具有两个或者更多独立核的组件。

摩尔定律(Moore’s Law):

  • 摩尔定律是英特尔(Intel)创始人戈登·摩尔(Gordeon Moore)提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。

安迪-比尔定律(Andy and Bill’s Law):

  • 描述了硬件产商和软件产商之间的关系。即比尔要拿走安迪所给的(What Andy gives,Bill takes away)。个人电脑工业整个的生态链是这样的:以微软为首的软件开发商吃掉硬件提升带来的全部好处,迫使用户更新机器让惠普和戴尔等公司收益,而这些整机生产厂再向因特尔这样的半导体厂订货购买新的芯片、同时向Seagate等外设厂购买新的外设。在这中间,各家的利润先后得到相应的提升,股票也随着增长。各个硬件半导体和外设公司再将利润投入研发,按照摩尔定律制定的速度,提升硬件性能,为软件下一步更新软件、吃掉硬件性能做准备。当然,微软和其他软件开发商在吃掉大部分硬件提升好处的同时,或多或少地会给用户带来一些新东西。

反摩尔定律(Reverse Moore’s Law):

  • Google的前CEO埃里克·施密特提出的:如果你反过来看摩尔定律, 一个IT公司如果今天和18个月前卖掉同样多的、同样的产品,它的营业额就要降一半。IT界把它称为翻墨尔定律。反摩尔定律对于所有的IT公司来讲,都是非常可怕的,因为一个IT公司花了同样的劳动;却只得到以前一半的收入。反摩尔定律逼着所有的硬件设备公司必须赶上摩尔定律所规定的更新速度,而所有的硬件和设备生产厂商活得都是非常辛苦的。

计算机发展史上的鼻祖:

  • 图灵:最近半个世纪以来,世界计算机科学界的重大进步,离不开图灵等人的理论奠基作用和多方面的开创性研究成果。图灵是当之无愧的计算机科学和人工智能之父。甚至认为,他在技术上的贡献以及对未来世界的影响几乎可与牛顿、爱因斯坦等巨人比肩。图灵论文中的“用有限的指令和有限的存储空间可算尽一切可算之物”理论让当时所有的科学家震惊。美国计算机学会(ACM)的年度“图灵奖”,自从1966年设立以来,一直是世界计算机科学领域的最高荣誉,相当于科学界额诺贝尔奖。至今,中国人只有姚期智院士获该奖项。
  • 冯·诺伊曼:20世纪最重要的数学家之一,在现代计算机、博弈论、核武器和生化武器等诸多领域内有杰出建树的最伟大的科学全才之一,被后人称为“计算机之父”和“博弈论之父”。计算机基本工作原理是存储程序和程序控制,它是由世界著名数学家冯·诺伊曼提出的。最简单来说,冯诺依曼理论的要点是:数字计算机的数制采用二进制;计算机应该按照程序顺序执行。
  • 同样有着“计算机之父”称号的冯·诺伊曼的助手弗兰克尔在一封信中写道“…计算机的基本概念属于图灵。按照我的看法,冯·诺伊曼的基本作用是使世界认识了由图灵引入的计算机基本概念…”。

根据冯·诺伊曼体系结构构成的计算机,必须具有如下功能:

  • 把需要的程序和数据送至计算机中。
  • 必须具有长期记忆程序、数据、中间结果及最终运算结果的能力。
  • 能够完成各种算术、逻辑运算和数据传送等数据加工处理的能力。
  • 能够根据需要控制程序走向,并能根据指令控制机器的各不见协调操作。
  • 能够按照要求将处理结果输出给用户。

1.2.2 比特和字节

概念:

  • 计算机就是一系列的电路开关。每个开关存在两种状态:关(off)和开(on)。简单而言,在计算机中存储信息就是将一系列的开关设置为开或者关。

比特的概念:

  • 如果电路是开的,它的值是1。如果电路是关的,它的值是0。这些0和1被解释为二进制数字系统中的数,并且将它们称为比特(bit,二进制数)。比特是计算机中最小的存储单位。

字节的概念:

  • 计算机中字节(byte)是最小的存储单元。
  • 每个字节由8个比特构成。
  • 为了存储单个字节放不下的大数字,计算机需要使用几个字节。
  • 各种类型的数据(例如,数字和字符)都被编码为字节序列。

编码模式的概念:

  • 程序员不需要关心数据的编码和解码,这些都是系统根据编码模式(schema)来自动完成的。

  • 编码模式是一系列的规则,控制计算机将字符、数字和符号翻译成计算机可以实际工作的数据。

  • 大多数模式将每个字符翻译成预先确定的一个比特串。

存储单位:

  • 计算机的存储能力是以字节和多字节来衡量的。
  • 千字节(kilobyte,KB)大约是1000字节。
  • 兆字节(megabyte,MB)大约是100万字节。
  • 千兆字节(gigabyte,GB)大约是10亿字节。
  • 万亿字节(terabyte,TB)大约是1万亿字节。

1.2.3 内存

内存概念:

  • 计算机的内存有一个有序的字节序列组成,用于存储程序及程序需要的数据。
  • 一个程序和它的数据在被CPU执行之前必须移到计算机的内存中。
  • 每个字节都有一个唯一的地址。使用这个地址确定字节的位置,以便于存储和获取数据。
  • 因为可以按任意顺序存取字节,所以内存也被称为随机访问存储器(Random-Access Memory,RAM)。
  • 通常而言,一个计算机具有的RAM越多,它的运行速度越快,但是这条简单的经验法则是有限制的。
  • 内存中字节的内容永远非空,但是它的原始内容可能对你的程序来说是毫无意义的。内存中的原始内容是什么?
  • 一旦新的信息被放入内存,该字节的当前内容就会丢失。
  • 内存是带电存储的(一旦断电数据就会消失),而且容量有限,所以要长时间储存程序或数据就需要硬盘。

内存与CPU的区别:

  • 同CPU一样,内存也是构建在一个表面上嵌有数百万晶体管的硅半导体芯片上。与CPU相比,内存芯片更简单、更低速,也更便宜。

1.2.4存储设备

RAM的缺点:

  • 计算机的内存(RAM)是一种易失的数据保存形式;断电时存储在内存中的信息就会丢失。

存储设备的概念:

  • 程序和数据永久地存放在存储设备上,当计算机确实要使用它们时再移入内存,因为从内存读取比从存储设备读取要快得多。

存储设备主要的三种类型:

  • 磁盘驱动器
  • 光盘驱动器(CD和DVD)
  • USB闪存驱动器

驱动器的解释:

  • 驱动器(drive)是对存储介质进行操作的设备。存储介质物理地存储数据和程序指令。驱动器从介质读取数据并将数据写在介质上。

    1、磁盘

    • 每台计算机至少有一个硬盘驱动器。
    • 硬盘(hard disk)用于永久地存储数据和程序。
    • 此外,还有移动硬盘。

    2、光盘和数字化视频磁盘

    • CD的全称是致密的盘片(compact disc)。

    • 光盘驱动器的类型有两种:只读光盘(CD-R)和可读写光盘(CD-RW)。

    • 只读光盘上的信息只能用于读取,内容一旦记录到光盘上,用户是不能修改它们的。

    • 可读写光盘可以像硬盘一样使用。也就是说,可以将数据写到光盘上,然后用新的数据覆盖掉这些数据。

    • 单张光盘的容量可以达到700MB。

    • DVD的全称是数字化多功能碟片或者数字化视频磁盘。

    • DVD和CD看起来很像,可以使用任意一种来存储数据。

    • 一张DVD上可以保存的信息要比一张CD上可以保存的信息多。

    • 一张标准的DVD的存储容量是4.7GB。

    • 有两种类型的DVD:DVD-R(只读)和DVD-RW(可重写)。

    3、USB闪存驱动器

    • 通用串行总线(Universal serial Bus,USB)接口允许用户将多种外部设备连接到计算机上。

    • USB闪存驱动器(flash drive)适用于存储和传输数据的设备。


1.2.5输入和输出设备

概念:

  • 输入设备和输出设备让用户可以和计算机通信。
  • 最常用的输入设备是键盘(keyboard)和鼠标(mouse)。
  • 而最常用的输出设备时显示器(monitor)和打印机(printer)。

1、键盘

键盘是用于输入的设备。

  • 功能键(function key)位于键盘的最上边,而且都是以F为前缀。它们的功能取决于当前所使用的软件。

  • 修饰符键(numeric keypad)位于键盘的右下角,是一套独立的类似计算器风格的按键集合,用于快速输入数字。

  • 方向键(arrow key)位于主键盘和数字小键盘之间,在各种程序中用于上下左右地移动光标。

  • 插入键(Insert)、删除键(Delete)、向上翻页键(Page Up)和向下翻页键(Page Down)分别用于在字处理和其他程序中完成插入文本和对象、删除文本和对象以及向上和向下翻页的功能。

2、鼠标

鼠标(mouse)是定点设备,用来在屏幕上移动一个称为光标的图形化的指针(通常以一个箭头的形状),或者用于单击屏幕上的对象(如一个按钮)来触发它以执行动作。

3、显示器

显示器(monitor)显示信息(文本和图形)。

  • 屏幕分辨率和点距决定显示的质量。
  • 屏幕分辨率(screen resolution)是指显示设备水平和垂直维度上的像素数。
  • 像素(“图像元素”的简称)就是构成屏幕上图像的小点。
  • 分辨率可以手工设置。分辨率越高,图像越锐化、越清晰,显示效果越好。
  • 像素密度(PPI)计算公式:根号((长度像素)*(长度相像素)+(宽度像素)*(宽度像素)) / 屏幕尺寸

1.2.6通信设备

计算机可以通过通信设备进行联网。

通信设备种类:

  • 调制解调器(modulator/demodulator,调制器/解调器):
    • 拨号调制解调器使用的是电话线,传输数据的速度可以高达56_000bps(bps表示每秒比特)。
  • DSL:
    • DSL(Digital Subscriber Line,数字用户线)使用的也是标准电话线,但是传输数据的速度比标准拨号调制解调器快20倍。
  • 电缆调制解调器:
    • 电缆调制解调器利用电缆公司维护的有线电视电缆进行数据传输,通常速度比DSL快。
  • 有线网络接口卡:
    • 网络接口卡(NIC)是将计算机接入局域网(LAN)的设备。局域网通常用于大学、商业组织和政府组织。一种称为1000BaseT的高速NIC能够以每秒1000Mbps(Mbps表示每秒百万比特)的速度传输数据。
  • 无线适配器:
    • 无线网络现在在家庭、商业和学校中极其流行。计算机可以通过无线适配器连接到局域网和Internet上。


1.3编程语言

计算机程序概念:

  • 计算机程序(program)称为软件(software),是告诉计算机该做什么的指令.
  • 所有的程序都必须转换成计算机可以执行的指令。

什么是计算机语言:

  • 语言:是人与人之间用于沟通的一种方式。
  • 计算机语言:人与计算机交流的方式。

1.3.1 机器语言

计算机的原生语言因计算机类型的不同而有差异,计算机的原生语言就是机器语言(machine language),即一套内嵌的原子指令集。因为这些指令都是以二进制代码的形式存在,所以,为了以机器原生语言的形式给计算机指令,必须以二进制代码输入指令。


1.3.2 汇编语言

简介:

  • 汇编语言(assembly language)使用短的描述性单词(称为助记符)来表示每一条机器语言指令。
  • 汇编语言中的一条指令对应机器代码中的一条指令。用汇编语言写代码需要知道CPU是如何工作的。

汇编器:

  • 由于计算机不理解汇编语言,所以需要使用一种称为汇编器(assembler)的程序将汇编语言程序转换为机器代码。

汇编语言与机器语言:

  • 汇编语言被认为是低级语言,因为汇编语言本质上非常接近机器语言,并且是及其相关的。

1.3.3 高级语言

简介:

  • 20世纪50年代,新一代编程语言即众所周知的高级语言出现了。

  • 它们是平台独立的,这意味着可以使用高级语言编程,然后在各种不同类型的机器上运行。

  • 高级语言中的指令称为语句。

  • 用高级语言编写的程序称为源程序(Source Program)或源代码(source code)。由于计算机不能运行源程序,源程序必须被翻译成可执行的机器代码。

流行的高级语言:

  • Ada:用Ada Loveleace(她研究机械式的通用目的计算机)命名,Ads是为美国国防部开发的,主要用于国防项目。
  • BASIC:初学者通用符号指令代码,是为了让初学者易学易用而设计的
  • C:由贝尔实验室开发,C语句具有汇编语言的强大功能以及高级语言的易学性和可移植性。
  • C++:基于C语言开发,是一种面向对象程序设计语言。
  • C#:读为“C sharp”,由Mircrosoft公司开发的混合了Java和C++特征的语言
  • COBOL:面向商业的通用语言,是为商业应用而设计的。
  • FORTRAN:公式翻译,广泛应用于科学和数学应用
  • Java:由Sun公司(现在属于Oracle)开发,广泛应用于一些独立于平台的互联网应用程序
  • Pascal:以Blaise Pascal (Blaise Pascal是17世纪计算机器的先驱)命名,Pascal是一个简单的、结构化的、通用目的语,适合编写小程序
  • Python:一种简单的通用目的的脚本语言,适合编写小程序
  • Visual Basic:由Mircrosoft公司开发,方便编程人员快速开发图形用户界面。

TIOBE是一个流行编程语言排行榜。每月更新。排名权重基于世界范围内:工程师数量、课程数量和第三方供应商数量。Google Bing Yahoo!Wikipedia Amazon Yotube和百度这些主流的搜索引擎,也将作为排名权重的参考指标。声明。TIOBE排名既无关最好的编程语言,也无关被书写了最多行代码的编程语言。

解释器与编译器:

  • 翻译可以由另外一种称为解释器或者编译器的编程工具来完成。
  • 解释器从源代码中读取一条语句,将其翻译为机器代码或者虚拟机器代码,然后立刻运行。请注意来自源代码的一条语句可能被翻译为多条机器指令。
  • 编译器将整个源代码翻译为机器代码文件,然后执行该机器代码文件。


1.4 操作系统

简介:

  • 操作系统(Operating System,OS)是运行在计算机上的最重要的程序,它可以管理和控制计算机的活动。

操作系统的主要任务有:

  • 控制和监视系统的活动
  • 分配和调配系统资源
  • 调度操作

1.4.1 控制和监视系统的活动

操作系统执行基本的任务,例如,识别来自键盘的输入,向显示器发送输出结果,跟踪存储在设备中的文件和文件夹的动态,控制类似硬盘驱动器和打印机这样的外部设备。

操作系统还要确保不同的程序和用户同时使用计算机时不会互相干扰。

另外,操作系统还负责安全处理,以确保未经授权的用户和程序无权访问系统。


1.4.2 分配和调配系统资源

操作系统负责确定一个程序需要使用哪些计算机资源,并进行资源分配和调配以运行程序。


1.4.3 调度操作

操作系统负责调度程序的活动,以便有效地利用系统资源。

为了提高系统的性能,目前许多操作系统都支持多道程序设计(multiprogramming)、多线程(multithreading)和多处理(multiprocessing)这样的技术。

  • 多道程序设计允许多个程序通过共享CPU同时运行。CPU的速度比其他组件快得多,这样,多数时间他都处于空闲状态。多道程序设计操作系统利用这一点,允许多个程序同时使用CPU,一旦CPU空闲就让别的程序使用它。
  • 多线程允许单个程序同时执行多个任务。编辑和保存是同一个应用的两个不同任务,这两个任务可能并发运行。
  • 多处理也称为并行处理(parallel proccessing),是指使用两个或多个处理器共同并行执行子任务,然后将子任务的结果合并以得到整个任务的结果。


1.5 Java、万维网以及其他

Java简介:

  • Java是一种功能强大和多用途的编程语言,可用于开发运行在移动设备、台式计算机以及服务器端的软件。

  • Java是由James Gosling在Sun公司领导的小组开发的。

  • 2010年Sun公司被Oracle公司收购。

  • Java最初被称为Orak(橡树),是1991年为消费类电子产品的嵌入式芯片而设计的。

  • 1995年更名为Java,并重新设计用于开发Web应用程序。

  • 关于Java的历史,参见www.java.com/en/javahistory/index.jsp。

  • 关于Java特性的剖析,参见www.cs.armstrong/liang/JavaCharacteristics.pdf。

  • Java是功能完善的通用程序设计语言,可以用来开发健壮的任务关键的应用程序。

万维网:

  • 万维网(World Wide Web,WWW)是从世界上任何地方的Internet都可以访问的电子信息宝库。发明者蒂姆·伯纳斯·李。分为Web客户端和Web服务器程序。WWW可以让Web看客户端(常用浏览器)访问浏览Web服务器上的页面。是一个由许多互相连接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个“统一资源标识符(URL)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。
  • 万维网是无数个网络站点和网页的集合,它们在一起构成了因特网最主要的部分(因特网也包括电子邮件、Usenet以及新闻组)。它实际上是多媒体的集合,是由超级链接连接而成的。我们通常通过网络浏览器上网观看的,就是万维网的内容。
  • Internet作为万维网的基础架构。
  • Java一开始富有吸引力是因为Java程序可以在Web浏览器中运行。这种能在Web浏览器中运行的Java程序称为Java小程序(applet)。
  • applet使用现代的图形用户界面与Web用户进行交互,处理用户的要求,界面中包括按钮、文本字段、文本域、单选按钮等。
  • applet使得Web更加具有响应性、交互性和趣味性。

B/S和C/S:

  • B/S:Browser/Server,浏览器也可以算是一个特殊的客户端。
  • C/S:Client/Server

HTML:

  • HTML(Hypertext Markup Language)是一种简单的脚本语言,用于对文档布局,连接因特网上的文档,并且能够在万维网上提供生动的图像、声音和视频。

Java的其他作用:

  • 你可以使用Java开发富因特网引用(RIA)。富因特网引用作为一种Web引用,被设计为可以提供通常桌面桌面应用才具有的特性和功能。
  • 现在,Java广泛应用于开发服务器端的应用程序。这些应用程序处理数据、执行计算,并生成动态网页。
  • 用于安卓手机的软件也是采用Java进行开发的。

职业发展与提升:

  • 路线图1:助理工程师->初级工程师->中级工程师->高级工程师->经理/高级经理->总监->资深总监->高管
  • 路线图2:助理工程师->初级工程师->中级工程师->高级工程师->资深工程师/专员->专家->资深专家->首席专家


1.6 Java语言规范、API、JDK和IDE

Java语言规范:

  • Java语言规范定义了Java的语法。
  • 计算机语言有严格的使用规范。如果编写程序时没有遵循这些规范,计算机就不能理解程序。Java语言规范和JavaAPI定义了Java的标准。
  • Java语言规范(Java Language specification)是对语言的技术定义,包括Java程序设计语言的语法和语义。
  • 完整的Java语言规范可以在http://docs.oracle.com/javase/specs上找到。

Java库:

  • Java库则在JavaAPI中定义。
  • 应用程序接口(Application Program Interface,API)也称为库,包括为开发Java程序而预定义的类和接口。
  • 网址:http://download.java.net/jdk8/docc/api上查看和下载最新版的Java API。

JDK:

  • JDK是用于开发和运行Java程序的软件。
  • JDK是由一套独立程序构成的集合,每个程序都是从命令调用的,用于开发和测试Java程序。

IDE:

  • IDE是快速开发程序的集成开发环境。

Java的三个版本:

  • Java标准版(Java Standard Edition,Java SE)可以用来开发客户端的应用程序。应用程序可以独立运行或作为applet在Web浏览器中运行。

    • JavaSE是基础,其他Java技术都基于JavaSE。
    • JavaSE也有很多版本,Oracle发布Java的各个版本都带有Java开发工具包(Java Development Toolkit,JDK)。JavaSE8对应的Java开发工具包称为JDK1.8(也称为Java8或者JDK8)。
  • Java 企业版(Java Enterprise Edition,JavaEE)可以用来开发服务器端的应用程序,例如,Java servlet和JavaServer Pages(JSP),以及JavaServer Faces(JSF)。

  • Java微型版(Java MicroEdition,JavaME)用来开发移动设备的应用程序。



1.7 一个简单的Java程序

软件开发:

  • 软件,即一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分。

人机交互方式:

  • 图形化界面(Graphical User Interface,GUI)这种方式简单直观,使用者易于接受,容易上手操作。
  • 命令行方式(Command Line Interface,CLI)需要有一个控制台,输入特定的指令,让计算机完成一些操作。较为麻烦,需要记录一些命令。

Java是从类中的main方法开始执行的。

Java源程序是区分大小写的。

控制台:

  • 控制台(console)是一个老的计算机词汇,指计算机的文本输入和显示设备。控制台输入是指从键盘上接收输入,控制台输出是指在显示器上显示输出。

类:

  • 每个Java程序至少应该有一个类。类名都是以大写字母开头的。
  • 一个类可以包含几个方法。

方法:

  • 方法是包含语句的结构体。

字符串:

  1. 字符串(String)是一个编程术语,表示一个字符序列。一个字符串必须放入双引号中。

语句结束符:

  • Java中的每条语句都以分号(;)结束,也成为语句结束符(statement terminator)。

保留字和关键字:

  • 保留字(reserved word)或关键字(keyword)对编译器而言都是有特定含义的,所以不能在程序中用于其他目的。

注释:

  • 注释(comment),它标注该程序是干什么的,以及它是如何构建的。注释帮助程序员进行相互沟通以及理解程序。注释不是程序设计语言,所以编译器编译程序时是忽略注释的。
  • 在Java中,在单行上用两个斜杠(//)引导注释,称为行注释(line comment);在一行或多行用/*和*/括住注释,称为块注释(block comment)。当编译器看到//时,就会忽略之后的所有文本。当看到/*时,它会搜索接下来的*/,并忽略掉/*与*/之间的文本。意思就是注释不能嵌套。

类块和方法块:

  • 程序中的一对括号将程序的一些组成部分组合起来,形成一个块(block)。在Java中,每个块以左花括号({)开始,以右花括号(})结束。每个类都有一个将类的数据和方法放在一起的类块(class block)。每个方法都有一个将该方法中的语句放在一起的方法块(method block)。块是可以嵌套的,即一个块可以放到另一个块中内。一个左括号必须匹配一个右括号。

特殊字符:

  • {}:表示一个包含语句的块
  • ():和方法一起使用
  • []:表示一个数组
  • //:表示后面是一行注释
  • “”:包含一个字符串(即一系列的字符)
  • ;:标识一个语句的结束


1.8 创建、编译和执行Java程序

创建:

  • Java源程序保存为java文件,编译为class文件。class文件由java虚拟机(JVM)执行。

  • 可以使用任何一个文本编辑器或者集成开发环境来创建和编辑Java源代码文件。

  • 从命令窗口,可以使用文本编辑器比如记事本(NotePad)来创建Java源代码。

  • **注意:**源文件的扩展名必须是.java,而且文件名必须与公共类名完全相同。

编译:

  • Java编译器将Java源文件翻译成Java字节码文件。

字节码:

  • Java语言是高级语言,而Java字节码是低级语言。字节码类似于机器指令,但它是体系中立的,是可以在任何带Java虚拟机(JVM)平台上运行的。

  • 虚拟机不是物理机,而是一个解释Java字节码的程序。

  • Java字节码可以在不同的硬件平台和操作系统上运行。Java源代码编译成Java字节码,然后Java字节码被JVM解释执行。你的Java代码可能要用到Java库中的代码。JVM将执行你的程序代码以及库中的代码。

执行:

  • 执行Java代码就是运行程序的字节码,可以在任何一个装有JVM的平台上运行字节码,解释Java字节码。解释的过程就是一次将字节码中单独的一步翻译为目标机器语言代码,而不是将整个程序翻译成单独的一块。翻译完一步就立即执行这一步。

  • **警告:**在命令行执行程序时,不要使用扩展名.class。

  • **提示:**如果要运行一个不存在的类,就会出现NoClassDefFoundError的错误。如果执行的类文件中没有main方法或者敲错了main方法,则会出现提示NoSuchMethodError。

注意:在执行一个Java程序时,JVM首先会用一个称为类加载器(class loader)的程序将类的字节码加载到内存中。如果你的程序中使用其他类,类加载程序会在需要它们之前动态地加载它们。当加载该类后,JVM使用一个称为字节码验证器(bytecode verifier)的程序来检验字节码的合法性,确保字节码不会违反Java的安全规范。Java强制执行严格的安全规范,以确保来自网络的Java程序不会篡改和危害你的计算机。



1.9 程序设计风格和文档

简介:

  • 良好的程序设计风格和正确的文档使程序更易阅读,并且能帮助程序员避免错误。

程序设计风格:

  • 程序设计风格(programming style)决定程序的外观。

  • 如果把整个程序写在一行,它也会被正确地编译和运行,但是这是非常不好地程序设计风格,因为程序的可读性很差。

文档:

  • 文档(documentation)是关于程序的解释性评注和注释的一个结构体。

良好的程序设计风格和适当的文档可以减少出错的机率,并且提高程序的可读性。


1.9.1 正确的注释和注释风格

在程序的开头写一个总结,解释一下这个程序是做什么的、其主要特点以及所用到的独特技术。

在较长的程序中还要加上注释,介绍每一个主要步骤并解释每个难以读懂之处。

除了行注释和块注释之外,Java还支持一种称为Java文档注释(javadoc comment)的特殊注释形式。javadoc注释以/**开始,以*/结尾。它们能使用JDK的javadoc命令提取一个HTML文件。

使用javadoc注释(/***/)来注释整个类或整个方法。为了将这些注释提取出来放在一个javadocHTML文件中,这些注释必须放在类或者方法头的前面。

要注释方法中的某一步骤,使用行注释。


1.9.2 正确的缩进和空白

缩进(identation)用于描述程序中组成部分或语句之间的结构性关系。

即使将程序的所有语句都写在一行中,Java也可以读懂这样的程序。

在嵌套结构中,每个内层的组成部分或语句应该比外层缩进两格。

二元操作符的两边应该各加一个空格。


1.9.3 块的风格

块是由花括号围起来的一组语句。

块的写法有两种常用方式:次行(next-line)风格和行尾(end-of-line)风格。

次行风格将括号垂直对其,因而使程序容易阅读。而行尾风格更节省空间,并有助于避免犯一些细小的程序设计错误。



1.10 程序设计错误

程序设计错误可以分为三类:语法错误、运行时错误和逻辑错误。


1.10.1 语法错误

在编程过程中出现的错误称为语法错误(syntax error)或编译错误(compile error)。语法错误是由创建代码时的错误引起的。这些错误通常很容易检测到,因为编译器会告诉你这些错误在哪儿,以及是什么原因造成的。

由于一个错误常常会显示很多行的编译错误,因此,从最上面的行开始向下纠正错误是一个很好的习惯。


1.10.2 运行时错误

运行时错误(runtime error)是引起程序非正常中断的错误。

运行应用程序时,当环境检测到一个不可能执行的操作时,就会出现运行时错误。

输入错误是典型的运行时错误。另一个常见的运行时错误是0作除数。


1.10.3 逻辑错误

当程序没有按预期的方式执行时就会发生逻辑错误(logic error)。这种错误发生的原因有很多种。

Java中,整数相除是返回除法的整数部分,即小数部分被截掉。

通常情况下,因为编译器可以明确指出错误的位置以及出错的原因,所以语法错误是很容易发现和纠正的。运行时错误也不难找,因为在程序异常中止时,错误的原因和位置都会显示在控制台上。然而,查找逻辑错误就很富有挑战性。


1.10.4 常见错误

1、遗漏右括号:括号用来标识程序中的块。每个左括号必须有一个右括号匹配。常见的错误是遗漏右括号号。为避免这个错误,任何时候输入左括号的时候就输入右括号。如果使用NetBeans和Eclipse这样的IDE,IDE将自动为每个输入的左括号插入一个右括号。

2、遗漏分号:每个语句都以一个语句结束符(;)结束。通常,编程入门者会忘了在一个块的最后一行语句后加上语句结束符。

3、遗漏引号:字符串必须放在引号中。如果使用NetBeans和Eclipse这样的IDE,IDE将自动为每个输入的左括号插入一个右引号。

4、命名拼写错误:Java是大小写敏感的。



1.11 使用NetBeans开发Java程序

NetBeans和Eclipse是两个开发Java程序的免费的流行集成开发环境。

如果按照简单的指南学习,可以很快掌握。


1.11.1 创建Java工程
1.11.2 创建Java类
1.11.3 编译和运行类


本章小结

1、计算机是存储和处理数据的电子设备。

2、计算机包括硬件和软件两部分。

3、硬件是计算机中可以触摸到的物理部分。

4、计算机程序,也就是通常所说的软件,是一些不可见的指令,它们控制硬件完成任务。

5、计算机程序设计就是编写让计算机执行的指令(即代码)

6、中央处理器(CPU)是计算机的大脑。它从内存获取指令并且执行这些指令。

7、计算机使用0或1,因为数字设备有两个稳定的状态,习惯上就是指0和1。

8、一个比特就是指二进制数0或1。

9、一个字节是指8比特的序列。

10、千字节大约是1000字节,兆字节大约是100万字节,千兆字节大约是10亿字节,万亿字节大约是1万亿字节。

11、内存存储CPU要执行的数据和程序指令。

12、内存单元是字节的有序序列。

13、内存是不能长久地保存在存储设备里,当计算机确实需要使用它们时被移入内存。

14、程序和数据永久地保存在存储设备里,当计算机确实是需要使用它们时被移入内存。

15、机器语言是一套内嵌在每台计算机的原始指令集。

16、汇编语言是一种低级程序设计语言,它用助记符表示每一条机器语言的指令。

17、高级语言类似英语,易于学习和编写程序。

18、用高级语言编写的程序称为源程序。

19、编译器是将源程序翻译成机器语言程序的软件。

20、操作系统(OS)是管理和控制计算机活动的程序。

21、Java是平台无关的,这意味着只需编写一次程序,就可以在任何计算机上运行。

22、Java程序可以内嵌在HTML网页内,通过Web浏览器下载,给Web客户带来生动的动画和灵活的交互性。

23、Java源程序文件名必须和程序中的公共类名一致,并且以扩展名.java结束。

24、每个类都被编译成一个独立的字节码文件,该文件名与类名相同,扩展名为.class

25、使用javac命令可以从命令行编译Java源代码文件。

26、使用java命令可以从命令行运行Java类。

27、每个Java程序都是一套类的定义集合。关键字class引入类的定义,类的内容包含在块内。

28、一个块以左括号({)开始,以右花括号(})结束。

29、方法包含在类中。每个可执行的Java程序必须有一个main方法。main方法是程序开始执行的入口。

30、Java中的每条语句都是以分号(;)结束,也称该符号为语句结束符。

31、保留字或者称关键字,对编译器而言都有特殊含义,在程序中不能用于其他目的。

32、在Java中,在单行上用两个斜杠(//)引导注释,称为行注释;在一行或多行用//包含注释,称为块注释或者段注释。编译器会忽略注释。

33、Java源程序是区分大小写的。

34、编程错误可以分为三类:语法错误、运行时错误和逻辑错误。编译器报告的错误称为语法错误或者编译错误。运行时错误指引起非正常结束的错误。当一个程序没有按照预期的方式执行时,产生逻辑错误。





第2章 基本程序设计

2.1 引言
2.2 编写简单的程序

概念:

  • 编写程序涉及如何涉及解决问题的策略,以及如何应用编程语言实现这个策略。
  • 编写程序涉及如何设计算法以及如何将算法翻译成程序指令,即代码。

算法:

  • 算法描述的是:如何要解决问题,所需要执行的动作以及这些动作执行的顺序。
  • 算法可以帮助程序员在使用程序设计语言编写程序之前做一个规划。算法可以用自然语言或者伪代码(即自然语言和程序设计代码混在一起使用)。

类:

  • 每个Java程序都是以一个类的声明开始,在声明里类名紧跟在关键字class后面。

main方法:

  • 每个Java应用程序都必须有一个main方法,程序从该方法处开始执行。

变量:

  • 为了存储变量,需要声明一个称作变量的符号。
  • 变量代表了存储在计算机内存中的一个值。
  • 变量名应该尽量选择描述性的名字(descriptive name)。
  • 每个变量都有名字、类型、大小和值。
  • 直到给变量赋一个数值时,该变量才被定义。

数据类型:

  • 为了让编译器知道变量是什么 ,需要指明它们的数据类型,即存储在变量中的数据的类型,是整数、实数,或者其他。这称为声明变量。

  • Java提供简单数据类型来表示整数、实数、字符以及布尔类型。这些类型称为原始数据类型或基本类型。

  • 实数(即带小数点的数字)在计算机中使用一种浮点的方法来表示。因此,实数也称为浮点数。

  • Java中,可以使用关键字double来声明一个浮点变量。其表示变量是浮点数形式存储在计算机中的。

加号(+)有两种意义:一种用途是做加法,另一种用途是做字符串的连接(合并)。如果一个字符串和一个数值连接,数值将转化为字符串然后再和另外一个字符串连接。

**警告:**在源代码中,字符串常量不能跨行。



2.3 从控制台读取输入

Scanner类:

  • 可以使用Scanner类从控制台输入。
  • Java使用System.out来表示标准输出设备,而用System.in 来表示标准输入设备。默认情况下,输出设备是显示器,而输入设备是键盘。
  • Scanner类在包java.util里。
  • Java并不直接支持控制台输入,但是可以使用Scanner类创建它的对象,以读取来自System.in的输入。
Scanner input = new Scanner(System.in);

//代码解释:
语法new ScanenrSystem.in)表明创建了一个Scanner类型的对象。
语法Scanner input声明input是一个Scanner类型的引用变量。
整行的Scanner input = new Scanner(System.in)表明创建了一个Scanner对象,并且将它的引用值赋值给变量input。
    
对象可以调用它自己的方法。调用对象的方法就是让这个对象完成某个任务。
    
    

println和print的区别:

  • 当显示完字符串之后,println会将光标移动到下一行,而print不会将光标移动到下一行。

import语句有两种类型:

  • 明确导入(specific import)和通配符导入(wildcard import)。

  • 明确导入是在import语句中指定单个的类。

  • 通配符导入是指通过使用星号作为通配符,导入一个包中的所有类。

  • 除非要在程序中使用某个类,否则关于被导入包中的这些类的信息在编译时或运行时是不被导入的。导入语句只是告诉编译器在什么地方能找到这些类。声明明确导入和声明通配符导入在性能上是没有什么差别的。

IPO:

  • 大多数程序分三个步骤执行,即输入、处理和输出,这被称为IPO。输入是从用户那里获得输入,处理是使用输入产生结果,而输出是显示结果。


2.4 标识符

简介:

  • 标识符是为了标识程序中诸如类、方法和变量的元素而采用的命名
  • Java编译器会检测出非法标识符,并且报语法错误。
  • Java是区分大小写的。
  • 标识符是用于命名程序中的变量、方法、类和其他项

所有的标识符必须遵从以下规则:

  • 标识符是由字母、数字、下划线、和美元符号构成的字符序列。
  • 标识符必须以字母、下划线或美元符号开头,不能以数字开头
  • 标识符不能是保留字
  • 标识符不能是true、false或null。
  • 标识符可以为任意长度。

**提示:**不要使用 命 名 标 识 符 。 习 惯 上 , 字 符 命名标识符。习惯上,字符 只用在机器自动产生的源代码中。



2.5 变量

简介:

  • 变量用于表示在程序中可能被改变的值。它们被称为变量是因为它们的值可以被改变。

  • 变量用于表示特定类型的数据。为了使用变量,可以通过告诉编译器变量的名字及其可以存储的数据类型来声明该变量。

  • 变量声明告诉编译器根据数据类型为变量分配合适的内存空间。(根据数据类型来分配大小,但引用类型如何确定分配的大小呢?)

  • 如果一个变量为同一类型,允许一起声明它们。变量之间用逗号分隔开。

  • 变量通常都有初始值。

  • 可以一步完成变量的声明和初始化。

  • 也可以使用简捷的方式来同时声明和初始化同一类型的变量。

  • 在赋值给变量之前,必须声明变量。方法中声明的变量在使用之前必须被赋值。

  • 每个变量都有使用范围。变量的使用范围是指变量可以被引用到的程序的部分。变量的作用域问题

  • 一个变量在使用前,必须被声明和初始化。



2.6 赋值语句和赋值表达式

赋值语句:

  • 赋值语句将一个值指定给一个变量。
  • 在Java中赋值语句可以作为一个表达式。<并不能用在while条件语句中。即使单独声明不行。
  • 声明变量之后,可以使用赋值语句(assignment statement)给它赋一个值。

赋值表达式:

  • 在Java中,将等号(=)作为赋值操作符(assignment operator)。

  • 表达式(expression)表示涉及值、变量和操作符的一个运算,它们组合在一起计算出一个新值。

  • 变量也可用在表达式中。变量也可以用于=操作符的两边。

  • 要给一个变量赋值,变量名必须在赋值操作符的左边。

  • 在Java中,赋值语句本质上就是计算出一个值并将它赋给操作符左边变量的一个表达式。由于这个原因,赋值语句常常称作赋值表达式(assignment expression)。.

  • 并不能用于条件判断。

  • 在赋值语句中,左边变量的数据类型必须与右边值的数据类型兼容。体现在多态中,子类对象赋值给父类的引用。



2.7 命名常量

简介:

  • 命名常量是一个代表不变值的标识符。
  • 一个变量的值在程序执行过程中可能会发生变化,但是命名常量(named constant)或简称常量,则表示从不改变的永久数据。
  • 常量必须在同一条语句中声明和赋值。单词final是声明常量的关键字。

语法格式:

final datatype CONSTANTNAME = value;

使用常量的三个好处:

  • 1、不必重复输入同一个值;
  • 2、如果必须修改常量值,只需在源代码中的一个地方做改动。
  • 3、给常量赋一个描述性的名字会提高程序易读性。


2.8 命名习惯

应该确保程序中为变量、常量、类和方法所选择的描述性名字是直观易懂的。

  • 包名:多单词组成时所有字母都小写
  • 变量和方法(小驼峰):使用小写字母命名变量和方法。如果一个名字包含多个单词,就将它们连在一起,第一个单词的字母小写,而后面的每个单词的首字母大写。
  • 类、接口名:(大驼峰):类名中的每个单词的首字母大写。
  • 常量(大写+下划线):大写常量中的所有字母,两个单词间用下划线连接。


2.9 数值数据类型和操作

Java针对整数和浮点数有六种数值类型,以及+、-、*、/、和%等操作符。


2.9.1 数值类型

简介:

  • 每个数据类型都有它的取值范围。
  • 编译器会根据每个变量的数据类型为其分配内存空间。
  • Java为数值、字符值和布尔值数据类型提供了八种基本数据类型。
  • 只有7种基本数据类型可以进行变量间的运算。不包含boolean类型。
  • String可以和八种类型做运算。都为拼接。
数值数据类型
类型名 范围 存储大小
byte -128~127(2^7) 8位带符号数
short -32768~32767(2^15) 16位带符号数
int -2_147_483_648~2_147_483_647(2^31) 32位带符号数
long -2^63~2^63-1 64位带符号数
float 负数范围:-3.40282235E+38~-1.4E-45 正数范围:1.4E-45~3.4028235E+38 32位,标准IEEE754
double ........ 64位,标准IEEE754
IEEE 754:
  • IEEE 754是美国电气电子工程师协会通过的一个标准,用于在计算机上表示浮点数。该标准已被广泛采用。Java采用32位IEEE 754 表示float型,64位IEEE 754表示double型。IEEE 754标准还定义了一些特殊浮点值。

整数类型:

  • Java使用四种类型的整数:byte、short、int和long。应该为变量选择最适合的数据类型。

浮点数类型:

  • Java使用了两种类型的浮点数:float和double。double型是float型的两倍。所以,double又称为双精度(double precision),而float称为单精度(single precision)。
  • 通常情况下,应该使用double型,因为它比float型更精确。

2.9.2 从键盘读取数值(Scanner类的方法)
Scanenr对象的方法
方法 描述
nextByte() 读取一个byte类型的整数
nextShort() 读取一个short类型的整数
nextInt() 读取一个int类型的整数
nextLong() 读取一个long类型的整数
nextFloat() 读取一个float类型的整数
nextDouble() 读取一个double类型的整数
如果你输入了一个不正确范围或者格式的值,将产生一个运行时错误。
2.9.3 数值操作符

简介:

  • 数值数据类型的操作符包括标准的算数操作符:加号、减号、乘号、除号、求余号。
  • 操作数是被操作符操作的值。

操作符:

  • 当除法的操作数都是整数时,除法的结果就是整数,小数部分被舍去。为了实现浮点数的除法,其中一个操作数必须是浮点数。
  • 操作符%被称为求余或者取模操作符,可以求得除法的余数。左边的操作数是被除数,右边的操作数是除数。
  • 操作符%通常用在正整数上,实际上,它也可用于负整数和浮点值。只有当被除数是负数时,余数才是负的。
  • 操作符+和-可以是一元也可以是二元的。一元操作符仅有一个操作数;而二元操作符有两个操作数。
  • 使用除法时要特别小心。在Java中,两个整数相除商为整数。

2.9.4 幂运算

简介:

  • 使用方法Math.pow(a,b)来计算a^b。
  • pow方法定义在JavaAPI的Math类中。

语法格式:

Math.pow(a,b);


2.10 数值型直接量

一个直接量(literal)是一个程序中直接出现的常量值。


2.10.1 整型直接量

整型直接量:

  • 只要整型直接量与整型变量相匹配,就可以将整型直接量赋值给该整型变量。
  • 整型直接量默认为是int型的。

注意事项:

  • 如果直接量太大,超出该变量的范围,就会出现编译错误。
  • 为了表示一个long型的直接量,需要在其后面追加字母L或l。

**注意:**默认情况下,整形直接量是一个十进制数。要表示一个二进制整数直接量,使用0B或者0b(零B)开头;表示一个八进制整数直接量,就用0(零)开头,而要表示一个十六进制整数直接量,就用0x或0X(零x)开头。


2.10.2 浮点型直接量

浮点型直接量:

  • 浮点型直接量浮点型直接量带小数点,

注意事项:

  • 默认情况下是double型的。
  • 可以通过在数字后面追加字母f或F表示该数为float型直接量,也可以在数字后面加d或D表示该数为double型直接量。

Java整数常量默认是int类型,当用二进制定义整数时,其第32位是符号位;当是long类型时,二进制默认占64位,第64位是符号位。

二进制整数有如下三种形式:

  • 原码:直接将一个数值转换成二进制数。最高位是符号位。
  • 负数的反码:是对原码按位取反,只是最高位(符号位)确定为1.
  • 负数的补码:其反码加1.

计算机以二进制补码的形式保存所有的整数。

  • 正数的原码、反码、补码都相同。
  • 负数的补码是其反码+1。

2.10.3 科学计数法

科学计数法:

  • 浮点型直接量也可以用a x 10^b形式的科学计数法表示。
  • E(或e)表示指数,既可以是大写的也可以是小写的。

注意:float型和double型都是用来表示带有小数点的数。

为什么把它们称为浮点数呢?因为这些数都是以科学计数法的形式进行内部存储的。

注意:为了提高可读性,Java允许在数值直接量的两个数字间使用下划线。下划线必须置于两个数字之间。



2.11 表达式求值以及操作符优先级

表达式求值:

  • Java表达式的求值和数学表达式求值是一样的。
  • 尽管Java有自己在后台计算表达式的方法,但是,Java表达式的结果和它对应的算术表达式的结果是一样的。

操作符优先级:

  • 首先执行的是包括在圆括号里的运算。圆括号可以嵌套,嵌套时先计算内层括号。
  • 当一个表达式中有多于一个操作符时,以下操作符的优先级规则用于确定计算的次序:
    • 乘法、除法和求余运算首先计算。如果表达式中包含若干个乘法、除法和求余操作符,可按照从左到右的次序执行。
    • 最后执行加法和减法运算。如果表达式中包含若干个加法和减法操作符,则按照从左到右的顺序执行。


2.12 示例学习:显示当前时间

可以通过调用System.currentTimeMillis()返回当前的时间。

System类中的方法currentTimeMillis返回从GMT1970年1月1日00:00:00开始到当前时刻的毫秒数。

时间戳是时间开始记时的点,因为1970年是UNIX操作系统正式发布的时间,所以这一时间也称为UNIX时间戳(UNIX epoch)。



2.13 增强赋值操作符

使用场景:

  • 经常会出现变量的当前值被使用、修改,然后再重新赋值给该变量的情况。

操作符+、-、*、/、%可以结合赋值操作符形式形成增强操作符。

Java允许使用增强赋值操作符来结合赋值和加法操作符的功能。

增强赋值操作符在表达式中所有其他操作符计算完后执行。

在增强赋值操作符中是没有空格的。

注意:就像赋值操作符(=)一样,增强赋值操作符既可以构成赋值语句,也可以构成赋值表达式。(不能用于条件判断,但能用于输出)

x += 2; //Statement
System.out.println(x += 2); //Expression


2.14 自增和自减操作符

自增操作符(++)和自减操作符(–)是对变量进行加1和减1的操作。

++ 和 – 是对变量进行自增1和自减1的简写操作符。

++和–放在变量后面,这些操作符分别称为后置自增操作符和后置自减操作符。

++和–放在变量前面,这些操作符分别称为前置自增操作符和前置自减操作符。

自增和自减操作符
操作符 名称 说明
++var 前置自增操作符 变量var的值加1且使用var增加后的新值
var++ 后置自增操作符 变量var的值加1但使用var原来的值
--var 前置自减操作符 变量var的值减1且使用var减少后的新值
var-- 后置自减操作符 变量var的值减1但使用var原来的值
单纯指进行自增和自减时,前置操作符和后置操作符效果是一样的。但是,当用在表达式中不单纯只进行自增和自减时,它们就会产生不同的效果。

2.15 数值类型转换

如果在一个二元运算中,其中一个操作数是整数,而另一个操作数是浮点数,Java会自动地将整数转换为浮点值。

总是可以将一个数值赋给支持更大数值范围类型的变量。但是,如果不进行类型转换,就不能将一个值赋给范围较小类型的变量。

类型转换是一种将一种数据类型的值转换为另一种数据类型的操作。

拓宽类型(widening a type):将一个小范围类型的变量转换为大范围类型的变量称为拓宽类型。

byte、char、short -->int —>long ---- float — >double

缩窄类型(narrowing a type):把大范围类型的变量转换为小范围类型的变量称为缩窄类型。

Java将自动拓宽一个类型,但是,缩窄类型必须显式完成。

类型转换的语法要求目标类型放在括号内,紧跟其后的是要转换的变量名或值。

**警告:**如果要将一个值赋给一个范围较小类型的变量,就必须进行类型转换。如果在这种情况下没有使用类型转换,就会出现编译错误。使用类型转换时必须小心,丢失的信息也许会导致不精确的结果。

当byte、char、short三种类型的变量做运算时,结果为int型。为什么?

注意:类型转换不改变被转换的变量。

注意:在增强赋值表达式中,使用的是被操作数的数据类型



2.16 软件开发过程

软件开生命周期是一个多阶段的过程,包括需求规范、分析、设计、实现、测试、部署和维护u。

开发一个软件产品是一个工程过程。软件产品,无论多大或者多小,具有同样的生命周期:需求规范、分析、设计、实现、测试、部署和维护。

  • 需求规范是一个规范化的过程,旨在理解软件要处理的问题,以及将软件系统需要做的详细记录到文档中。这个阶段设计用户和开发者之间紧密的接触。
  • 系统分析旨在分析数据流,并且确定系统的输入和输出。当进行分析的时候,首先确定输出,然后弄清楚需要什么样子的输入从而产生结果是有帮助的。
  • 系统设计式设计一个从输入获得输出的过程。这个阶段设计使用多层的抽象,将问题分解为可管理的组成部分,并且设计执行每个组成部分的策略。可以将每个组成部分看作一个执行系统特定功能的子系统。系统分析和设计的本质是输入、处理和输出(IPO)。
  • 实现是将系统设计翻译成程序。为每个组成部分编写独立的程序,然后集成在一起工作。这个过程需要使用一门编程语言。实现包括编码、自我测试,以及调试(即,在代码中寻找错误,称为调试)。
  • 测试确保代码符合需求规范,并且排除错误。通常由一个没有参与产品设计和实现的独立软件工程团队完成这样的测试。
  • 部署使得软件可以被使用。按照软件类型的不同,可能被安装到每个用户的机器上,或者安装在一个Internet可访问的服务器上。
  • 维护是对软件产品进行更新和改进。软件产品必须在一直烟花的环境中连续运行和改进。这要求产品周期性改进,以修正新发现的错误,并且将更改集成到产品中。


2.17 示例学习:整钱兑零
import java.util.Scanner;

/**
 *
 * 程序清单2-10
 *
 * 将一个大额的钱分成小货币单位
 *
 * 假如你希望开发一个程序,将给定的钱数分成较小的货币单位。这个程序要求用户输入一个double型的值,该值是用美元和美分表示的总钱数,然后输出一个清单,列出和总钱数等价的最大数量的dollar(1美元)、quarter(2角5美分)、dime(1角)、nickel(5分)和penny(1分)的数目,按照这个顺序,从而使得硬币最少。
 *
 * 开发流程:
 * 1、提示用户输入十进制数作为总钱数,例如11.56
 * 2、将该钱数转换为1分币的个数
 * 3、通过将1分币的个数除以100,求出1美元的个数。通过对1分币的个数除以100求余数,得到剩余1分币的个数。
 * 4、通过将剩余的1分币的个数除以25,求出2角5分币的个数。通过对剩余的1分币的个数除以25求余数,得到剩余1分币的个数。
 * 5、将剩余的1分币的个数除以10,求出1角币的个数。通过对剩余的1分币的个数除以10求余数,得到剩余1分币的个数。
 * 6、将剩余的1分币的个数除以5,求出5分币的个数。通过对剩余1分币的个数除以5求余数,得到剩余1分币的个数。
 * 7、剩余1分币的个数即为所求
 * 8、显示结果。
 *
 *
 *
 */
public class ComputeChange {
    public static void main(String[] args) {
		
        //Create a Scanner object
        Scanner input = new Scanner(System.in);

        //Receive the amount
        System.out.print("Enter an amount in double,for example 11.56: ");
        double amount = input.nextDouble();
		
        //Covert amount to pennies 
        int remainingAmount = (int)(amount * 100);

        //Find the number of one dollars
        int numberOfOneDollars = remainingAmount / 100;
        remainingAmount = remainingAmount % 100;

        //Find the number of quarters in the remaining amount
        int numberOfQuarters = remainingAmount / 25;
        remainingAmount = remainingAmount % 25;

        //Find the number of dime in the remaining amount
        int numberOfDime = remainingAmount / 10;
        remainingAmount = remainingAmount % 10;

        //Find the number of nickel in the remaining amount
        int numberOfNickel = remainingAmount / 5;
        remainingAmount = remainingAmount % 5;

        //Find the number of pennies in the remaining amount
        int numberOfPennies = remainingAmount;

        //Display results
        System.out.println("Your amount " + amount + " consists of ");
        System.out.println("        " + numberOfOneDollars + " dollars");
        System.out.println("        " + numberOfQuarters + " quarters");
        System.out.println("        " + numberOfDime + " Dimes");
        System.out.println("        " + numberOfNickel + " nickel");
        System.out.println("        " + numberOfPennies + " pennies");
        
        
        /*
        
        本例的一个严重问题是将一个double型的总数转换为int型数remainingAmount时可能会损失精度,这会导致不精确的结果。如果输入的总额值为10.03,那么10.03*100就会编程1002.99999999999999,程序会显示10个1美元和2个1美分。为了解决这个问题,应该输入用美分表示的整型值。
         */
    }

}



2.18 常见错误和陷阱

常见的基础编程错误经常涉及未声明变量、未初始化变量、整数溢出、超出预期的整数除法,以及数值取整错误。

常见错误1、未声明、未初始化的变量和未使用的变量。

  • 变量必须在使用之前声明为一个类型并且赋值。
  • 如果声明了一个变量,但是没有在程序中用到,将是一个潜在的编程错误。因此,你应该从程序中将未使用的变量移除。

常见错误2:整数溢出

  • 数字以有限的位数存储。当一个变量被赋予一个过大(以存储大小而言)的值,以至于发存储该值,这称为溢出。
  • Java不会给出关于溢出的警告或者错误,因此,当处理一个给定类型的最大和最小范围很接近的数值时,要特别小心。
  • 如果存储的浮点数很小(例如,接近于0),这会引起向下溢出。Java会将他近似为0,所以一般情况下不用考虑向下溢出的问题。

常见错误3:取整错误

  • 一个取整错误,也称为凑整错误,是在计算得到的数字的近似值和确切的算数值之间的不同。
  • 因为一个变量保存的位数是有限的, 因此取整错误是无法避免的。
  • 涉及浮点数的计算都是近似的,因为这些数没有以准确的精度来存储。
  • 整数可以精确地存储。因此,整数计算得到的是精确的整数运算结果。

常见错误4:超出预期的整数除法

  • Java使用同样的除法操作符来执行整数和浮点数的除法。当两个操作数是整数时,/操作符将执行一个整数除法,操作的结果是整数,小数部分被截去。要强制两个整数执行一个浮点数除法时,将其中一个整数转换为浮点数值。

常见陷阱:冗余的输入对象

  • 编程新手经常为每个输入编写代码创建多个输入对象。


本章小结

1、标识符是程序中用于命名诸如变量、常量、方法、类、包之类元素的名称。

2、标识符是由字符、数字、下划线和美元符号构成的字符序列。标识符必须以字母或下划线开头,不能以数字开头。标识符不能是保留字。标识符可以为任意长度。

3、变量用于存储程序中的数据。声明变量就是告诉编译器变量可以存储何种数据类型。

4、有两种类型的import语句:明确导入和通配符导入。明确导入是在import语句中指定导入单个类;通配符导入将包中所有的类导入。

5、在Java中,等号被用作赋值操作符。

6、方法中声明的变量必须在使用前被赋值。

7、命名常量(或简称为常量)表示从不改变的永久数据。

8、用关键字final声明命名常量。

9、Java提供四种整数类型(byte、short、int、long)表示四种不同长度范围的整数。

10、Java提供两种浮点类型(float、double)表示两种不同精度的浮点数。

11、Java提供操作符完成数值运算:加号、减号、乘号、除号和求余符号

12、整数运算(/)得到的结果是一个整数。

13、Java表达式中的数值操作符和算数表达中的使用方法是完全一致的。

14、Java提供扩展赋值操作符:+=(加法赋值)、-=(减法赋值)、*=乘法赋值、/=除法赋值以及%=(求余赋值)。

15、自增操作符(++)和自减操作符(–)分别对变量加1或减1.

16、当计算的表达式中有不同类型的值,Java会自动地将操作数转换为恰当的类型。

17、可以使用(type)value这样的表示法显式地将数值从一个类型转换到另一个类型。

18、将一个较小范围地变量转换为较大范围类型地变量称为拓宽类型。

19、将一个较大范围的变量转换为较小范围的变量称为缩窄类型。

20、扩宽类型不需要显式转换,可以自动完成。缩窄类型必须显式完成。

21、在计算机科学中 ,1970年1月1日午夜零点称为UNIX时间戳。





第3章 选择

3.1 引言

和所有高级程序设计语言一样,Java也提供选择语句:在可选择的执行路径中做出选择的语句。

选择语句要用到采用布尔表达式的条件。

布尔表达式是计算结果为Boolean值:true或者false的表达式。



3.2 boolean数据类型

boolean数据类型声明一个具有值true或者false的变量。

Java提供六种关系操作符(relational operator)(也称为比较操作符(compaarison operator)),用于两个值的比较。

关系操作符
Java操作符 名称
< 小于
<= 小于等于
> 大于
>= 大于等于
== 等于
!= 不等于

**警告:**相等的关系操作符是两个等号(==),而不是一个等号(=),后者是指赋值操作符。

具有布尔值的变量称为布尔变量(boolean variable),boolean数据类型用于声明布尔型变量。

boolean型变量可以是以下这两个值中的一个:true和false。

true和false都是直接量,就像10这样的数字。它们被当作保留字一样,不能用做程序中的标识符。



3.3 if语句

简介:

  • if语句是一个结构,允许程序确定执行的路径。
  • Java有几种类型的选择语句:单分支if语句、双分支if-else语句、嵌套if语句、多分支if-else语句、switch语句和条件表达式。

单分支语句:

  • 单分支if语句是指当且仅当条件为true时执行一个动作。
  • 布尔表达式应该用括号括住。
  • 如果布尔表达式计算的结果为true,则执行块内语句。
  • 如果花括号内只有一条语句,则可以省略花括号。
  • 当指定条件为true时单分支if语句执行一个操作。而当条件为false时什么也不干。
  • 单分支if语句的语法格式如下:
if(布尔表达式){
	语句();
}

流程图:

  • 流程图是描述算法或者过程的图,以各种盒子显示步骤,并且通过箭头连接给出次序。处理操作显示在这些盒子中,连接它们的箭头代表控制流程。棱形的盒子表示一个布尔类型的条件,矩形盒子代表语句。

**注意:**省略花括号可以让代码更加简短,但是容易产生错误。当你返回去修改略去代码的时候,容易忘记加上括号。这是一个常犯的错误。



3.4 双分支if-else语句

简介:

  • 根据条件为true或false,双分支if语句可以指定不同的操作。
  • 如果布尔表达式的计算结果为true,则执行条件为true时该执行的语句;否则,执行条件为fasle时该执行的语句。
  • 通常,如果花括号中只有一条语句,则可以省略花括号。
  • 双分支if-else语句的语法:
if(布尔表达式){
    布尔表达式为真时执行的语句();
}
else{
    布尔表达式为假时执行的语句();
}


3.5 嵌套的if语句和多分支if-else语句
  • if或if-else语句中的语句可以是任意合法的Java语句,甚至可以是其他的if或if-else语句。

  • 内层if语句称为是嵌套在外层if语句里的。

  • 内层if语句还可以包含其他的if语句;事实上,对嵌套的深度没有限制。

  • 嵌套的if语句可用于实现多重选择。

  • 注意:只有在前面的所有条件都为false时才测试下一个条件。



3.6 常见错误和陷阱

常见错误1:忘记必要的括号

  • 如果块中只有一条语句,就可以忽略花括号。但是,当需要用花括号将多条语句括在一起时,忘记花括号是一个常见的程序设计错误。
  • 如果通过在没有花括号的if语句中添加一条新语句来修改代码,就必须插入花括号。

常见错误2:在if行出现错误的分号

  • 在if行加上了一个分号,这是一个常见的错误。相当于定义了一个空的块。
  • 这个错误是很难发现的,因为它既不是编译错误也不是运行错误,而是一个逻辑错误。
  • 当使用换行块风格时,经常会出现这个错误。所以使用行尾块风格可帮助防止出现此类错误。

常见错误3:对布尔值的冗余测试

  • 为了检测测试条件中的布尔型变量是true还是false而使用相等比较操作符是多余的。比较好的替代方法就是直接测试布尔变量。
  • 使用=操作符而不是==操作符去比较测试条件中的两项是否相等是一个常见的错误。(even=true)这条语句没有编译错误。

常见错误4:悬空else出现的歧义

  • 本应匹配外层if语句的else语句,但是却匹配的是内层if语句,这种现象就称为悬空else歧义(dangling-else ambiguity)。在同一个块中,else总是匹配和离它最近的if子句匹配。

常见错误5:两个浮点数值的相等测试

  • 浮点数具有有限的计算精度;涉及浮点数的计算可能引入取整错误。因此,两个浮点数值的相等测试并不可靠。
  • 虽然不能依赖于两个浮点数值的相等测试,但是可以通过测试两个数的差距小于某个阈值,来比较它们是否已经足够接近。
  • 也就是,对于一个非常小的值e,如果|x-y|-14来比较两个double类型的值,而设为10-7来比较两个float类型的值。
  • Math.abs(a)方法可以用于返回a的绝对值。

常见陷阱1:简化布尔变量赋值

if(number%2==0)
    even = true;
else 
    even = false;

boolean even = numnber%2 == 0;

常见陷阱2:避免不同情形中的重复代码

  • 编程新手经常会在不同情形下写重复的代码,这些代码应该写在一处的。

  • if(inState){
        tuition=5000;
        System.out.println("The tuition is " + tuition);
    }else{
        tuition = 15000;
        System.out.println("The tuition is " + tuition);
    }
    //-------------------------------------------------
    if(inState){
        tuition = 5000;
    }else{
        tuition = 15000;
    }
    System.out.println("The tuition is " + tuition);
        
    


3.7 产生随机数

你可以使用Math.random()来获得一个0.0到1.0之间的随机double值,不包括1.0调用这个方法会返回一个双精度的随机值d且满足0.0<=d<1.0。



3.8 示例学习:计算身体质量指数
3.9 示例学习:计算税率

System.exit(status)是在System类中定义的, 调用这个方法可以终止程序。参数status为0表明程序是正常结束。一个非0的状态代码表示非正常结束。结束正在运行的JVM虚拟机。

如果变量变量赋值语句在if语句中,编译器认为这些语句可能不会执行,因此会报告一个编译错误。所以变量需要在条件判断语句外初始化。

System.exit和break和continue的区别是什么?

所有程序都应该先编写少量代码然后进行测试,之后再继续添加更多的代码。这个过程称为递进式开发和测试(incremental development and testing)。这种方法使得调式变得更加容易,因为错误很可能就在你刚刚添加进去的新代码中。



3.10 逻辑操作符

简介:

  • 逻辑操作符!、&&、||和^可以用于产生复合布尔表达式。

  • 有时候,是否执行一条语句是由几个条件的组合来决定的。可以使用逻辑操作符组合这些条件。

  • 逻辑操作符(logical operator)也称为布尔操作符(boolean operator),是对布尔值进行的运算,它会创建新的布尔值。

逻辑操作符说明:

  • 非操作符(!)对true取反是false,而false取反之后则是true。

  • 与操作(&&)。当且仅当两个操作数都为true时,这两个布尔型操作数的与(&&)为true。如果操作符&&的操作数之一为false,那么表达式就是false;

  • 或操作符(||),当至少有一个操作数为true时,两个布尔型操作数的或(||)为true。如果操作符||的操作数之一为true,那么表达式就是true。

  • 异或(^)操作符:当且仅当两个操作数具有不同的布尔值时,两个布尔型 操作数的异或(^)才为true。

布尔操作符
操作符 名称 说明
! 逻辑非
&& 逻辑与
|| 逻辑或
^ 异或 逻辑异或
布尔值和数值是不兼容的。无法进行相互转换。

德模佛定理

  • 是以印度出生的英国数学家和逻辑学家奥古斯都·德·模佛来命名的(1806-1871),这个定理可以用来简化表达式。

  • 内容:

    • !(condition1 && condition2) 等价于 !condition1 || !condition2
    • !(condition1 || condition2) 等价于 !condition1 && !condition2

在编程术语中,&&和||被称为短路或者懒惰操作符;Java也提供了无条件AND(&)和OR(|)操作符。



3.11 示例学习;判定闰年
import java.util.Scanner;

/**
 *
 * 程序清单3-7
 *
 * 如果某年你可以被4整除而不能被100整除,或者可以被400整除,那么这一年就是闰年
 *
 * 可以使用下面的表达式判定某年是否为闰年。
 *
 * //A leap Year is divisible by4
 * boolean isLeanYear = ( year % 4 == 0 )
 *
 * //A leap Year is divisible by 4 but not by 100
 * isLearYear = isLeapYear && (year % 100 != 0);
 *
 * //A leap Year is divisible by 4 but not by 1000 or divisible by 400
 * is LeapYear = isLeapYear || ( year % 400 == 0)
 *
 */
public class LeapYear {
    public static void main(String[] args) {
        //Create a Scanner object
        Scanner input = new Scanner(System.in);

        //Promtp the user to enter an Integer for years
        System.out.print("Enter a year");
        int year = input.nextInt();

        //Check if the year is a leap year
        boolean isLeapYear = (year % 4 == 0 ) || (year % 400 == 0);

        //Display the result
        System.out.println(year + " is a leap year? " + isLeapYear);

    }
}


3.12 示例学习:彩票
import java.util.Scanner;

/**
 *
 * 程序清单3-8
 *
 * 彩票程序涉及产生随机数、比较数字各位,以及运用布尔操作符。
 *
 *
 * 假设你想开发一个玩彩票的游戏,程序随机地产生一个两位数的彩票,提示用户输入一个两位数,然后按照下面的规则判定用户是否能赢:
 * 1、如果用户的输入数匹配彩票的实际顺序,奖金为10000美元。
 * 2、如果用户输入的所有数字匹配彩票的所有数字,奖金为3000美元.
 * 3、如果用户输入的一个数字匹配彩票的一个数字,奖金为1000美元。
 *
 * 注意,两位数字的位可能为0.如果一个数小于10,我们假设这个数字以0开始,从而构建一个两位数。
 * 例如,程序中数字8被作为08处理,数字00作为00处理。
 *
 *
 */
public class Lottery {
    public static void main(String[] args) {

        //Generate a lottery number
        int lottery = (int)(Math.random()* 100);

        //Prompt the user to enter a guess
        Scanner input = new Scanner(System.in);
        System.out.print("Enter your lottery pick(two digits): ");
        int guess = input.nextInt();

        //Get digits form lottery
        int lotteryDigit1 = lottery / 10;
        int lotteryDIgit2 = lottery % 10;

        //Get digits form guess
        int guessDigits1 = guess / 10;
        int guessDigits2 = guess % 10;

        //Print the lottery number
        System.out.println("The lottery number is " + lottery);

        //Check the guess
        if(guess == lottery){
            System.out.println("Exact match: you win $10,000");
        }else if(guessDigits2 == lotteryDigit1 && guessDigits1 == lotteryDIgit2){
            System.out.println("Match all digits: you win $3,000");
        }else if(guessDigits1 == lotteryDigit1
               || guessDigits1 == lotteryDIgit2
               || guessDigits2 == lotteryDigit1
               || guessDigits2 == lotteryDIgit2)
            System.out.println("Match one digit: you win $1,000");
        else
            System.out.println("Sorry,no match");


    }

}


3.13 switch语句

简介:

  • switch语句基于变量或者表达式的值来执行语句。
  • Java提供switch语句来有效地处理多重条件的问题。
  • 一旦匹配其中一个case,就从匹配的case处开始执行,直到遇到brek语句或到达switch语句的结束。这种现象称为落空行为(fall-through behavior)。

switch语句遵从以下规则:

  • 1、switch表达式必须能计算出一个char、byte、short、int或者String型值,并且必须总是要用括号括住。
  • 2、值必须于switch表达式的结果值具有相同的数据类型。注意:case值都是常量表达式,也就是说这里的表达式是不能包含变量的。
  • 3、当switch表达式的值于case语句的值相匹配时,执行从该case开始的语句,直到遇到一个break语句或到达switch语句的结束。
  • 4、默认情况下,default是可选的,当没有一个给出的case与switch表达式相匹配时,用来执行该操作。
  • 5、关键字break时可选的,。break语句会立即终止switch语句
    • 可选陷阱:case穿透
  • 6、case后面的值,不能重复。

JDK14:

  • JDK14支持的语法,case后面可以匹配多个值,多个值要用逗号。
  • JDK开始支持的语法:把:替换为-> ,把要执行的代码放到{}中,break不需要写。{}中只有一条语句,{}可以省略不写。

为了避免程序设计错误,提高代码的可维护性,如果故意省略break,在case子句后添加注释是一个好的做法。

default语句的顺序没有硬性规定。可以放在最前面。



3.14 条件表达式(三元运算符)

简介:

  • 条件表达式基于一个条件计算表达式的值。

  • 有时可能需要给有特定条件限制的变量赋值。

  • 条件表达式是一种完全不同的风格,在语句中没有明确出现if。

  • 如果布尔表达式的值为true,则条件表达式的结果为expression1;否则,结果为表达式expression2.

  • 符号?和:在条件表达式中同时出现。它们构成一种条件操作符,因为操作数有三个,所以称为三元操作符(ternary operator)。它是Java中唯一的三元操作符。

语法格式:

boolean-expression ? expression1 : expression2(布尔表达式?表达式1:表达式2


3.15 操作符的优先级和结合规则

优先级:

  • 操作符的优先级和结合规则确定了操作符计算的顺序。

  • 应该首先计算括号中的表达式(括号可以嵌套,在嵌套的情况下,先计算里层括号中的表达式)。当计算没有括号的表达式时,操作符会按照优先级规则和结合规则进行运算。

  • 优先级规则定义了操作符的先后次序:逻辑操作符的优先级比关系操作符的低,而关系操作符的优先级比比算数操作符的低。

结合规则:

  • 如果优先级相同的操作符相邻,则结合规则(associativity)决定它们的执行顺序。除了赋值操作符之外,所有的二元操作符都是左结合的(left-associative)。赋值操作符是右结合的(right-associative)。
优先级
运算符
名称或含义
使用形式
结合方向
说明
1
后置++
后置自增运算符
变量名++
左到右
 
后置-- 后置自减运算符 变量名--  
[ ]
数组下标
数组名[整型表达式]
 
( )
圆括号
(表达式)/函数名(形参表)
 
.
成员选择(对象)
对象.成员名
 
->
成员选择(指针)
对象指针->成员名
 
2
-
负号运算符
-表达式
右到左
单目运算符
(类型)
强制类型转换
(数据类型)表达式
 
前置++
前置自增运算符
++变量名
单目运算符
前置--
前置自减运算符
--变量名
单目运算符
*
取值运算符
*指针表达式
单目运算符
&
取地址运算符
&左值表达式
单目运算符
!
逻辑非运算符
!表达式
单目运算符
~
按位取反运算符
~表达式
单目运算符
sizeof
长度运算符
sizeof 表达式/sizeof(类型)
 
3
/
表达式/表达式
左到右
双目运算符
*
表达式*表达式
双目运算符
%
余数(取模)
整型表达式%整型表达式
双目运算符
4
+
表达式+表达式
左到右
双目运算符
-
表达式-表达式
双目运算符
5
<<
左移
表达式<<表达式
左到右
双目运算符
>>
右移
表达式>>表达式
双目运算符
6
>
大于
表达式>表达式
左到右
双目运算符
>=
大于等于
表达式>=表达式
双目运算符
<
小于
表达式<表达式
双目运算符
<=
小于等于
表达式<=表达式
双目运算符
7
==
等于
表达式==表达式
左到右
双目运算符
!=
不等于
表达式!= 表达式
双目运算符
8
&
按位与
整型表达式&整型表达式
左到右
双目运算符
9
^
按位异或
整型表达式^整型表达式
左到右
双目运算符
10
|
按位或
整型表达式|整型表达式
左到右
双目运算符
11
&&
逻辑与
表达式&&表达式
左到右
双目运算符
12
||
逻辑或
表达式||表达式
左到右
双目运算符
13
?:
条件运算符
表达式1? 表达式2: 表达式3
右到左
三目运算符
14
=
赋值运算符
变量=表达式
右到左
 
/=
除后赋值
变量/=表达式
 
*=
乘后赋值
变量*=表达式
 
%=
取模后赋值
变量%=表达式
 
+=
加后赋值
变量+=表达式
 
-=
减后赋值
变量-=表达式
 
<<=
左移后赋值
变量<<=表达式
 
>>=
右移后赋值
变量>>=表达式
 
&=
按位与后赋值
变量&=表达式
 
^=
按位异或后赋值
变量^=表达式
 
|=
按位或后赋值
变量|=表达式
 
15
,
逗号运算符
表达式,表达式,…
左到右
从左向右顺序运算


3.16 调试

概念:

  • 因为编译器可以明确指出错误的位置以及出错的原因,所以语法错误是很容易发现和纠正的。

  • 运行时错误也不难找,因为在程序异常中止时,错误的原因和位置都会显示在控制台上。

  • 然而,查找逻辑错误就很富有挑战性。

  • 逻辑错误也称为臭虫(bug)。查找和改正错误的过程称为调试(debugging)。

调试方法:

  • 调试的一般途径时采用各种方法逐步缩小程序中bug所在的范围。
  • 可以手工跟踪(hand trace)程序(即通过读程序找错误),
  • 也可以插入打印语句,显示变量的值或程序的执行流程。这种方法适用于短小、简单的程序。
  • 对于庞大、复杂的程序,最有效的调试方法还是使用调试工具。

调试工具:

  • JDK包含了一个命令行调试器jdb,结合一个类名来调用该命令。Jdb本身也是一个java程序,运行自身的一个Java解析器的拷贝。

  • 所有的Java IDE工具,比如Eclipse和NetBeans包含集成的调试器。

  • 调试器应用让你可以跟踪一个程序的执行。它们因系统而不同。

调试器应用的特征:

  • 一次执行一条语句:

    • 调试器允许你一次执行一条语句,从而可以看到每条语句的效果。
  • 跟踪进入或者一步运行过一个方法:

    • 如果一个方法正在被执行,你可以让调试器跟踪进入方法内部,并且一次执行方法里面的一条语句,或者你也可以让调试器一步运行过整个方法。如果你知道方法是可行的,你应该一次运行过整个方法。比如,通常都会一步运行过系统提供的方法。System.out.println。
  • 设置断点:

    • 你也可以在一条特定的语句上面设置断点。当遇到一个断点时,你的程序将暂停。你可以设置任意多的断点。当你知道程序错误从什么地方可能开始的时候,断点特别有用。你可以将断点设置在那条语句上,让程序先执行到断点处。
  • 显示变量:

    • 调试器让你选择多个变量并且显示它们的值。当你跟踪一个程序的时候,变量的内容持续更新。
  • 显示调用堆栈:

    • 调试器让你跟踪所有的方法调用。当你需要看到程序执行流程的宏观图景的时候,这个特征非常有用。
  • 修改变量:

    • 一些调试器允许你在调试的过程中修改变量的值。当你希望用不同的示例来测试程序,而又不希望离开调试器的时候,这是非常方便的。


本章小结

1、boolean类型变量可以存储值true或false。

2、关系操作符(<、<=、==、!=、>、>=)产生一个布尔值。

3、选择语句用于可选择的动作路径的编程。选择语句有以下几种类型:单分支if语句、双分支if-else语句、嵌套if语句、多分支if-else语句、switch语句和条件表达式。

4、各种if语句都是基于布尔表达式来控制决定的。根据表达式的值是true或false,这些语句选择两种可能路径中的一种。

5、布尔操作符&&、||、|和^对布尔值和布尔变量进行计算。

6、当对p1&&p2求值时,Java先求p1的值,如果p1为true,再对p2求值;入宫p1为false,就不再对p2求值。当对p1||p2求值时,Java先求p1的值,如果p1为false,再对p2求值;如果p1为true,就不再对p2求值。因此,&&也称为条件与操作符或短路与操作符,而||也称为条件或操作符或短路或操作符。

7、switch语句根据char、byte、short、int或者String类型的switch表达式来进行控制决定。

8、在switch语句中,关键字break是可选的,但它通常用在每个分支的结尾,以中止执行switch语句的剩余部分。如果没有出现break语句,则执行接下来的case语句。

9、表达式中的操作符按照括号、操作符优先级以及操作符结合规则所确定的次序进行求值。

10、括号用于强制求值的顺序以任何顺序进行。

11、具有更高优先权的操作符更早地进行操作。对于同样优先级地操作符,它们的结合规则确定操作符的顺序。

12、除开赋值操作符的所有二元操作符都是左结合的,赋值操作符是右结合的。





第4章 数学函数、字符和字符串

4.1 引言
4.2 常见数学函数

Java在Math类中提供了许多实用的方法,来计算常用的数学函数。

方法是一组语句,用于执行一个特定的任务。

Math中的方法分为三类:

  • 三角函数方法(trigonometric method)
  • 指数函数方法(expontent method)
  • 服务方法(service method)。包括取整、求最小值、求最大值、求绝对值和随机方法。

除了这些方法之外,Math类还提供了两个很有用的double型常量,PI和E(自然对数的底)。可以在任意程序中用Math.PI和Math.E的形式来使用这两个常量。


4.2.1 三角函数方法
  • sin(radians):返回以弧度为单位的角度的三角正弦函数值。
  • cos(radians):返回以弧度为单位的三角余弦函数值
  • tan(radians):返回以弧度为单位的三角正切函数值。
  • toRadians(degree):将以度为单位值的角度转换为以弧度表示
  • toDegrees(radians):将以弧度为单位的角度值转换为以度表示。
  • asin(a):返回以弧度为单位的角度的反三角正弦函数值。
  • acos(a):返回以弧度为单位的角度的反三角函数值。
  • atan(a):返回以弧度为单位的反三角正切函数值。

sin、cos和tan的参数都是以弧度为单位的角度。asin和atan的返回值是-PI/2到PI/2的一个弧度值,acos的返回值在0到PI之间。1°相当于PI/180弧度,90°相当于PI/2弧度,30°相当于PI/6弧度。


4.2.2 指数函数方法
  • exp(x):返回e的x次方。
  • log(x):返回x的自然底数
  • log10(x)返回x的以10为底的对数
  • pow(a,b):返回a的b次方
  • sqrt(x):对于x>=0的数字,返回x的平方根。

4.2.3 取整方法

Math类包括五个取整方法。

  • ceil(x):x向上取整为它最接近的整数。该整数作为一个双精度值返回。
  • floor(x):x向下取整为它最接近的整数。该整数作为一个双精度值返回。
  • rint(x):x取整为它最接近的整数。如果x与两个整数的距离相等,偶数的整数作为一个双精度值返回。
  • round(x):如果x是单精度数,返回(int)Math.foor(x+0.5);如果x是双精度数,返回(long)Math.floor(x+0.5)

4.2.4 min、max和abs方法
  • 求最小值

    • min方法用于返回两个数(int、long、float或double型)的最小值。
  • 求最大值

    • max方法用于返回两个数(int、long、float或double型)的最大值。
  • 求绝对值

    • abs方法以返回一个数(int、long、float或double型)的绝对值。

4.2.5 random方法

Math.random()生成大于等于0.0且小于1.0的double型随机数。可以使用它编写简单的表达式,生成任意范围的随机数。


4.2.6 示例学习:计算三角形的角度

Math类在程序中使用,但是并没有导入,因为它在java.lang包中。在一个Java程序中,java.lang包中的所有类是隐式导入的。

import java.util.Scanner;

/**
 * 程序清单4-1
 * 

* * 例子: * 给定一个三角形的三条边,可以通过以下公式计算角度。 * A = acos( (a * a - b * b -c * c) / (-2 * b *c)) * B = acos( (b * b - a * a -c * c) / (-2 * a *c)) * C = acos( (c * c - b * b -a * a) / (-2 * a *b)) * * 提示:两点之间的距离公式:sprt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ) * * * 提示用户输入三角形三个顶点的x和y坐标值,然后显示三个角 */ public class ComputeArea { public static void main(String[] args) { //Create a Scanner Object Scanner input = new Scanner(System.in); //Prompt the user to enter three points System.out.println("Enter three points: "); double x1 = input.nextDouble(); double y1 = input.nextDouble(); double x2 = input.nextDouble(); double y2 = input.nextDouble(); double x3 = input.nextDouble(); double y3 = input.nextDouble(); //Compute three sides double a = Math.sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3)); double b = Math.sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3)); double c = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); //Compute three angles double A = Math.toDegrees((a * a - b * b - c * c) / (-2 * b * c)); double B = Math.toDegrees((b * b - a * a - c * c) / (-2 * a * c)); double C = Math.toDegrees((c * c - b * b - a * a) / (-2 * a * b)); //Display results System.out.println("The three angles are " + Math.round(A * 100) / 100.0 + " " + Math.round(B * 100) / 100.0 + " " + Math.round(C * 100) / 100.0); } }



4.3 字符数据类型和操作

字符数据类型表示单个字符。

除了处理数值之外,Java还可以处理字符。

字符数据类型用char用来表示单个字符。

字符型直接量用单引号括住。

字符串直接两必须括在双引号中。而字符直接量是括在单引号中的单个字符。


4.3.1 Unicode和ASCII码

计算机内部使用二进制。一个字符在计算机中是以0和1构成的序列的形式来存储的。

编码:

  • 将字符映射到它的二进制形式的过程称为编码(encoding)。
  • 字符有多种不同的编码方式,编码表(encoding scheme)定义该如何编码每个字符。

Unicode:

  • Java支持Unicode码,Unicode码是由Unicode协会建立的一种编码方案,它支持使用世界各种语言所书写的文本的交换、处理和显示。

  • Unicode码一开始被设计为16位字符编码。基本数据类型char试图通过提供一种能够存放任意字符的简单数据类型来利用设计,但是,一个16位的编码能产生的字符只有65536个,它是不足以表示全世界所有字符的。因此,Unicode标准被扩展为1112064个字符。这些字符都远远超过了原来16位的限制,它们称为补充字符(supplementary character)。Java支持这些补充字符。这些做法都可以存储在一个char型变量中。

  • 一个16位Unicode码占两个字节,用以\u开头的4位十六进制数表示。

  • 自增自减操作符也可以用在char型变量上,这会得到该字符之前或之后的Unicode字符。

  • Unicode码包括ASCII码。

ASCII:

  • 大多数计算机采用ASCII码(美国标准信息交换码),它是表示所有大小写字母、数字、标点符号和控制字符的8位编码表。

Java中可以使用ASCII码,也可以使用Unicode码。


4.3.2 特殊字符的转义序列

概念:

  • Java定义了一种特殊的标记来表示特殊字符,这种标记称为转义序列。

  • 转义序列由反斜杠后面加上一个字符或者一些数字位组成。

  • 转义序列中的序列号作为一个整体翻译,而不是分开翻译。

  • 一个转义序列被当作一个字符。

  • 反斜杠\被称为转义字符。它是以一个特殊字符。要显示这个字符,需要使用转义序列\。

转义序列
转义序列 名称 Unicode码 十进制值
\b 退格键 \u0008 8
\t Tab键 \u0009 9
\n 换行符 \u000A 10
\f 换页符 \u000C 12
\r 回车符 \u000D 13
\\ 反斜杠 \u005C 92
\" 双引号 \u0022 34
---
4.3.3 字符型数据与数值型数据之间的转换(重点,必须记忆!!!)

字符:

  • char型数据可以转换成任意一种数值类型,反之亦然。
  • 将整数转换成char型数据时,只用到该数据的低十六位,其余部分都被忽略。

转换:

  • 要将一个浮点值转换成char型时,首先将浮点值转换成int型,然后将这个整形值转换为char型。

  • 当一个char型数据转换成数值型时,这个字符的Unicode码就被转换成某个特定的数值类型。

  • 如果转换结果适用于目标变量,就可以使用隐式转换方式;否则,必须使用显式转换方式。

  • 0~FFFF的任何一个十六进制正整数都可以隐式地转换成字符型数据。而不在此范围内的任何其他数值都必须显式地转换为char型。

  • 所有数值操作符都可以用在char型操作数上。

  • 如果另一个操作数是一个数字或字符,那么char型操作数就会被自动转换成一个数字。如果另一个操作数是一个字符串,字符就会与该字符串相连。


4.3.4 字符的比较和测试

两个字符可以使用关系操作符进行比较,如同比较两个数字一样。这是通过比较两个字符的Unicode值实现的。

小写字母的Unicode码是连续的整数,同理,对于大写字母和数字字符也是这样的。这个特征可以用于编写测试字符的编码。

Java的Character类中用于进行字符测试的方法:

Character类中的方法
方法 描述
isDigit(ch) 如果指定的字符是一个数字,返回true
isLetter(ch) 如果指定的字符是一个字母,返回true
isLetterOrDigit(ch) 如果指定的字符是一个字母或者数字,返回true
isLowerCase(ch) 如果指定的字符是一个小写字母,返回true
isUpperCase(ch) 如果指定的字符是一个大写字母,返回true
toLowerCase(ch) 返回指定的字符的小写形式
toUpperCase(ch) 返回指定的字符的大写形式
---
4.4 String类型

概念:

  • 字符串是一个字符序列。

  • char类型只能表示一个字符。为了表示一串字符,使用称为String(字符串)的数据类型。

  • String实际上与System类和Scanner类一样,都是Java库中一个预定义的类。

  • String类型不是基本类型,而是引用类型(reference type)。

任何Java类都可以将变量表示为引用类型。

使用引用类型声明的变量称为引用变量,它引用一个对象。

String对象的简单方法
方法 描述
length() 返回字符串中的字符数
charAt(index) 返回字符串s中指定位置的字符
concat(s1) 将本字符串和字符串s1连接,返回一个新字符串
toUpperCase() 返回一个新字符串,其中所有的字母大写
toLowerCase() 返回一个新字符串, 其中所有的字母小写
trim() 返回一个新字符串,去掉了两边的空白字符。
String是Java中的对象。上表中的这些方法只能从一个特定的字符串实例来调用。由于这个原因,这些方法称为 **实例方法**。非实例方法称为 **静态方法**。 **静态方法可以不使用对象来调用。**定义在Math类中的所有方法都是静态方法。它们没有绑定到一个特定的对象实例上。

调用实例方法的语法:一个方法可以有多个参数或者无参。

  • reference-Variable.methodName(arguments)

调用静态方法的语法:

  • ClassName.methodName(arguments)

4.4.1 求字符串长度

可以调用字符串的length()方法获取它的长度。

注意:使用一个字符串时,往往是知道它的直接量值的。为了方便起见,Java允许在不创建新变量的情况下,使用字符串直接量直接引用字符串。引用字符串常量池中的对象。?????

  • 例子:“Welocome to Java”.length()
  • 例子:“”.length() == 0

4.4.2 从字符串中获取字符

s.charAt(index)此方法可用于提取字符串s中的某个特定字符,其中下标index的取值范围在0~s.length()-1之间。

**警告:**在字符串S中越界访问字符是一种常见的程序设计错误。为了避免此类错误,要确保使用的下标不会超过s.length()-1。


4.4.3 连接字符串

可以使用concat方法连接两个字符串。

可以使用加号(+)连接两个或多个字符串。

若要用加号实现连接功能,至少要有一个操作数必须为字符串。如果操作数之一不是字符串,非字符串值转换为字符串,并于另外一个字符串连接。如果操作数都不是字符串,加号(+)是一个将两个数字相加的加法操作符。

增强的+=操作符也可以用于字符串连接。


4.4.4 字符串的转换

方法toLowerCase()返回一个新字符串,其中所有字母小写;

方法toUpperCase()返回一个新字符串,其中所有字母大写。

方法trim()通过删除字符串两端的空白字符返回一个新字符串

字符’ '、\t、\f、\r、或者\n被称为空白字符。


4.4.5 从控制台读取字符串

为了从控制台读取字符串,调用Scanner对象上的next()方法。

next()方法读取以空白字符结束的字符串(即’‘、’\t’、‘\f’、‘\r’或’\n’)。

可以用nextLine()方法读取一整行文本。nextLine()方法读取以按下回车键为结束标志的字符串。


4.4.6 从控制台读取字符

为了从控制台读取字符,调用nextLine()方法读取一个字符串,然后在字符串上调用charAt(0)来返回一个字符。


4.4.7 字符串比较
String对象的比较方法
方法 描述
equals(s1) 如果该字符串等于字符串s1,返回true。
equalsIgnoreCase(s1) 如果该字符串等于字符串s1,返回true;不区分大小写
compareTo(s1) 返回一个大于0、等于0、小于0的整数,表明一个字符串是否大于、等于或者小于s1
compareToIgnoreCase(s1) 和compareTo一样,除了比较是区分大小写的之外。
startWith(prefix) 如果字符串以特定的前缀开始,返回true
endsWith(suffix) 如果字符串以特定的后缀结束,返回true
contains(s1) 如果s1是该字符串的子字符串,返回true
==操作符只能检测两个字符串是否指向同一个对象,但它不会告诉你它们的内容是否相同。==因此,不能使用操作符判断两个字符串变量的内容是否相同。取而代之,应该使用equals方法。

equals用常量调用方法比较,避免空指针异常,变量有可能记录的是null。

compareTo方法也用来对两个字符串进行比较。如果s1于s2相等,那么该方法返回值0;如果按字典顺序(即Unicode码的顺序)s1小于s2,那么方法返回值小于0;如果按字典顺序s1大于s2,方法返回值大于0.方法compareTo返回的实际值是依据s1和s2从左到右第一个不同字符之间的距离得出的。

注意:如果两个字符串相等,equals方法返回true;如果它们不等,方法返回false。compareTo方法会根据一个字符串是否等于、大于或小于另一个字符串,分别返回0、正整数或负整数。

String类还提供了对字符串进行比较的方法equalsIgnoreCase和compareToIgnoreCase。当比较两个字符串时,方法equalsIgnoreCase和compareToIgnoreCase忽略字母的大小写。

还可以使用str.startsWith(prefix)来检测字符串是否以特定的前缀(prefix)开始,使用str.endWith(suffix)来检测字符串str是否以特定的后缀(suffix)结束,

并且可以使用str.contains(s1)来检测是否字符串str包含字符串s1.

警告:如果使用像>、>=、<、或<=这样的比较操作符比较两个字符串,就会发生语法错误。


4.4.8 获得子字符串

方法s.charAt(index)可用于提取字符串s中的某个特定字符。也可以使用String类中的substring方法从字符串中提取子串。

String类包含的获取子串的方法
方法 描述
substring(beginIndex) 返回该字符串的子串,从特定位置beginIndex的字符开始到字符串的结尾。
substring(beginIndex,endIndex 返回该字符串的子串,从特定位置beginIndex的字符开始到下标为denIndex-1的字符。注意,位于endIndex位置的字符不属于该子字符串的一部分。
---
4.4.9 获取字符串中的字符或者子串

String类提供了几个版本的indexOf和lastIndexOf方法,它们可以在字符串中找出一个字符或一个子串。

String类包含获取子串的方法
方法 描述
indexOf(ch) 返回字符串中出现的第一个ch的下标。如果没有匹配的,返回-1
indexOf(ch,fromIndex) 返回字符串中fromIndex之后出现的第一个ch的下标。如果没有匹配的,返回-1
indexOf(s) 返回字符串中出现的第一个字符串s的下标。如果没有匹配的,返回-1
indexOf(s,fromIndex) 返回字符串中出现的第一个字符串s的下标。如果没有匹配的,返回-1
lastIndex(ch) 返回字符串中出现的最后一个ch的下标。如果没有匹配的,返回-1
lastIndex(ch,fromIndex) 返回字符串中fromIndex之前出现的最后一个ch的下标。如果没有匹配的,返回-1
lastIndex(s) 返回字符串中出现的最后一个字符串s的下标。如果没有匹配的,返回-1
lastIndexOf(s,fromIndex) 返回字符串中fromIndex之前出现的最后一个字符串s的下标。如果没有匹配的,返回-1
----
4.4.10 字符串和数字间的转换

可以将数值型字符串转换为数值。要将字符串转换为int值,使用Interget.parseInt方法。

格式如下:

int intValue = Integer.parseInt(intString);
//intString是一个数值型字符串"123"

要将字符串转换为double值,使用Double.parseDouble方法。

格式如下:

double doubleValue = Double.parseDouble(doubleString);
//doubleString是一个数值型字符串,例如"123.54"

如果字符串不是数值型字符串,转换将导致一个运行时错误。Integer和Double类都包含在java.lang包中,因此它们是自动导入的。

可以将数值转换为字符串,只需要简单使用字符串的连接操作符。

String s = number + "";


4.5 示例学习

字符串在编程中是非常基础的内容。使用字符串进行编程的能力对于学习Java编程非常关键。


4.5.1 猜测生日
import java.util.Scanner;

/**
 *
 * 程序清单4-3
 *
 * 可以通过询问5个问题,找到他出生在一个月的哪一天。每个问题都询问生日是否是5个数字集合中的一个。
 *
 * {1    3    5   7,
 *  9    11   13  15,
 *  17   19   21  23,
 *  25   27   29  31}
 *
 * {2    3    6    7,
 *  10   11   14   15,
 *  18   19   22   23,
 *  26   27   30   31}
 *
 * {4    5    6    7,
 * 12    13   14   15,
 * 20    21   22   23,
 * 28    29   30   31}
 *
 * {8    9    10   11,
 *  12   13   14   15,
 *  24   25   26   27,
 *  28   29   30   31}
 *
 * {16   17   18   19,
 *  20   21   22   23,
 *  24   25   26   27,
 *  28   29   30   31}
 *
 *
 *  生日是出现这一天的每个集合的第一个数字的和。例如:如果生日是19,那么它会出现在集合1、集合2和集合5中。这三个集合的第一个数字分别是1、2和16.它们的和就是19.
 *
 *  提示用户回答该天是否在集合1中,是否在集合2中,是否在集合3中,是否在集合4中,是否在集合5中。如果这个数字在某个集合中,程序就将该集合的第一个数字加到day中去。
 *
 *
 */
public class GuessBirthday {

    public static void main(String[] args) {

        String set1 =
                "1    3    5   7\n" +
                "9    11   13  15\n" +
                "17   19   21  23\n" +
                "   25   27   29  31";

        String set2 =
                "2    3    6    7\n" +
                "10   11   14   15\n" +
                "18   19   22   23\n" +
                "26   27   30   31";

        String set3 =
                "4    5    6    7\n" +
                "12   13   14   15\n" +
                "20   21   22   23\n" +
                "28   29   30   31";

        String set4 =
                "8    9    10   11\n" +
                "12   13   14   15\n" +
                "24   25   26   27\n" +
                "28   29   30   31";

        String set5 =
                "16   17   18   19\n" +
                "20   21   22   23\n" +
                "24   25   26   27\n" +
                "28   29   30   31";

        int day = 0;

        //Create a Scanne object
        Scanner input = new Scanner(System.in);

        //Prompt the user to answer questions
        System.out.print("Is your birthday in Set1?\n");
        System.out.print(set1);
        System.out.print("\nEnter 0 for No and 1 for Yes:");
        int answer = input.nextInt();

        if(answer == 1)
            day += 1;

        //Prompt the user to answer question
        System.out.print("Is your birthday in Set2?\n");
        System.out.print(set2);
        System.out.print("\nEnter 0 for No and 1 for Yes:");
        answer = input.nextInt();

        if(answer ==1)
            day += 2;

        //Prompt the user to answer question
        System.out.print("Is your birthday in Set3?\n");
        System.out.print(set3);
        System.out.print("\nEnter 0 for No and 1 for Yes: ");
        answer = input.nextInt();

        if(answer ==1)
            day += 4;

        //Prompt the user to answer question
        System.out.print("Is your birthday in Set4?\n");
        System.out.print(set4);
        System.out.print("\nEter 0 for No and 1 for Yes:");
        answer = input.nextInt();

        if(answer == 1)
            day += 8;

        //Prompt the user to answer question
        System.out.print("Is your birthday in Set5?\n");
        System.out.print(set5);
        System.out.print("\nEnter 0 for No and 1 for Yes:");
        answer = input.nextInt();

        if(answer ==1)
            day += 16;

        System.out.println("\nYour birthday is " + day + "!");
        
        /*
        原理:
        
        实际上,这个游戏背后的数学知识是非常简单的。这些数字不是随意组成一组的。它们放在5个集合中的方式是经过深思熟虑的。这5个集合的第一个数分别是1、2、4、8、16,它们分别对应二进制数的1、10、100、1000和10000。从1到31的十进制数最多用5个二进制数就可以表示,假设它是b5b4b3b2b1,那么b5b4b3b2b1 = b50000 + b4000 + b300 +b20 + b1,如果某天的二进制数在bk位为1,那么该数就该出现在Setk中。例如:数字19的二进制形式是1001,所以它就该出现在集合1、集合2和集合5中。它就是二进制数1+10+10000=1011或者十进制数1+2+16 = 19.数字31的二进制形式是1111,所以它就会出现在集合1、集合2、集合3、集合4和集合5中。它就是二进制数1+10+100+1000+10000=11111,或者十进制数1+2+4+8+16 = 31。
         */

    }
}

4.5.2 将十六进制转换为十进制数

当两个字符执行数值运算的时候,计算机中使用的是字符的Unicode码。

import java.util.Scanner;

/**
 * 十六进制计数系统有16个数字:0~9,A~F。字母A、B、C、D、E和F对应于十进制数字10、11、12、13、14和15.我们现在写一个程序,提示用户输入一个十六进制的数字,显示它对应的十进制数。
 */
public class HexDigit2Dec {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("Enter a hex digit: ");
        String hexString = input.nextLine();

        //Check if the hex string has exactly one character
        if(hexString.length() != 1){
            System.out.println("You must enter exactly one character");
            System.exit(1);
        }

        //Display decimal value for the hex digit
        char ch = Character.toUpperCase(hexString.charAt(0));
        if(ch <='F' && ch >= 'A'){
            int value = ch - 'A'+ 10; //如果是十六进制字母,则将字母的Unicode码值减去10加上10
            System.out.println("The decimal value for hex digit " + ch + " is " + ch);
        }else if (Character.isDigit(ch)){
            System.out.println("The decimal value for hex digit " + ch + " is " + ch);
        }else{
            System.out.println(ch + " is an invalid input"); //invalid无效
        }
    }
}

4.5.3 使用字符串修改彩票程序
import java.util.Scanner;

/**
 * 程序清单4-5
 *
 * 程序清单3-8中的彩票程序产生一个随机的两位数字,提示用户输入一个两位数字,根据以下规则确定用户是否中彩票:
 * 1)如果用户输入的数字完全匹配彩票中的数字,奖金为10000美元
 * 2)如果用户输入的所有数字匹配彩票中的所有数字,奖金为3000美元
 * 3)如果用户输入的一个数字匹配彩票中的一个数字,奖金为1000美元。
 *
 * 程序清单3-8中的程序使用整数来存储数值。
 * 程序清单4-5给出一个新程序,产生一个随机的两位字符串,而不是数字,并且使用字符串而不是数字来接收用户输入。
 *
 *
 */
public class LotteryUsingStrings {
    public static void main(String[] args) {
        /*
        程序产生两个随机数,并且将它们连接成一个字符串lottery。这样,lottery包含两个随机数字。
        程序提示用户以两位字符串形式输入一个猜测值,并且按照以下顺序,对照彩票数字检测用户的猜测值:
            首先检测给出的猜测值是否完全匹配彩票
            如果不匹配,检测猜测值的逆序是否匹配彩票
            如果不匹配,检测是否有一个数字在彩票中。
            如果以上条件都不成立,显示“Sorry,no match”
         */

        //Generate a lottery as a two-digit string
        String lottery = "" + (int)(Math.random() * 10) + (int)(Math.random() * 10);

        //Prompt the user to enter a guess
        Scanner input = new Scanner(System.in);
        System.out.print("Enter your lottery pick(two digits): ");
        String guess = input.nextLine();

        //Get digits form lottery
        char lotteryDigit1 = lottery.charAt(0);
        char lotteryDigit2 = lottery.charAt(1);

        //Get digits form guess
        char guessDigit1 = guess.charAt(0);
        char guessDigit2 = guess.charAt(1);

        System.out.println("The lottery number is " + lottery);

        //Check the guess
        if(guess.equals(lottery))
            System.out.println("Exact match: you win $10,000.");
        else if(guessDigit1 ==lotteryDigit2 && guessDigit2 == lotteryDigit1)
            System.out.println("Exact match: you win $3,000.");
        else if(guessDigit1 == lotteryDigit1 || guessDigit1 == lotteryDigit2 || guessDigit2 == lotteryDigit1 || guessDigit2 == lotteryDigit2)
            System.out.println("Exact match: you win $1,000.");
        else
            System.out.println("Sorry,no match.");
    }
}


4.6 格式化控制台输出

可以使用System.out.printf方法在控制台上显示格式化输出。

%4.2f:

  • 4:域宽度
  • 2:精度
  • f:转换码
  • %4.2f:格式标识符

调用这个方法的语法是:

System.out.printf(format,item1,item2,.....,itemk);

这里的format是指一个由子串和格式标识符构成的字符串。

格式标识符指定每个条目应该如何显示。这里的条目可以是数值、字符、布尔值或字符串。

简单的格式标识符是以百分号(%)开头的转换码。

常用的格式标识符
标识符 输出 举例
%b 布尔值 true或false
%c 字符 'a'
%d 十进制数 200
%f 浮点数 45.46000000
%e 标准科学计数法形式的数 4.556000e+01
%s 字符串 "Java is cool"
条目必须在次序、数量和类型上匹配。

在格式标识符中,默认情况下,浮点值显示小数点后6位数字。

可以在标识符中指定宽度和精度。

  • %5c
    输出字符并在这个字符条目前加4个空格
  • %6b
    输出布尔值,在false值前加一个空格,在true值前加两个空格
  • %5d
    输出整数条目,宽度至少为5、如果条目的数字位数小于5,就在数字前面加空格。如果该条目的位数大于5,则自动增加宽度
  • %10.2f
    输出的浮点条目宽度至少为10,包括小数点和小数点后两位数字。这样,给小数点前分配了7位数字。如果该条目小数点前的位数小于7,就在数字前面加空格,如果该条目小数点前的位数大于7,则自动增加宽度。
  • %10.2e
    输出的浮点条目宽度至少为10,包括小数点和后两位数字和指数部分。如果按科学计数法显示的数字位数小于10,就给数字前加空格
  • %12s
    输出的字符串宽度至少为12个字符。如果该字符串条目小于12个字符,就在该字符串前加空格。如果该字符串条目多于12个字符,则自动增加宽度。

如果一个条目需要比指定宽度更多的空间,宽度自动增加。

默认情况下,输出是右对齐的。可以在格式标识符中放一个负号(-),表明该条目在特定区域中的输出是左对齐的。

**警告:**条目与格式标识符必须在类型上严格匹配。对于格式标识符%f或%e的条目必须是浮点型值。因此,int型变量不能匹配%f或%e。

**提示:**使用符号%来标记格式标识符,要在格式字符串中使用直接量%,需要使用%%。



本章小结

1、Java提供了在Math类中的数学方法sin、cos、tan、asin、acos、atan、toRadius、toDegree、exp、log、log10、pow、sqrt、cell、floor、rint、round、min、max、abs以及random,用于执行数学函数。

2、字符类型char表示单个字符

3、转义序列包含反斜杠\以及后面的字符或者数字组合。

4、字符\称为转义字符

5、字符’ '、\t、\f、\r和\n都称为空白字符。

6、字符可以基于它们的Unicode码使用关系操作符进行比较。

7、Character类包含方法isDigit、isLetter、isLetterOrDigit、isLowerCase、isUpperCase,用于判断一个字符是否是数字、字母、小写字母还是大写字母。它也包含toLowerCase和toupperCase方法返回小写字母或大写字母。

8、字符串是一个字符序列。字符串的值包含在一对匹配的双引号(")中。字符串的值包含在一对匹配的单引号中。

9、字符串在Java中是对象。只能通过一个指定对象调用的方法称为实例方法。非实例方法称为静态方法,可以不使用对象来调用。

10、可以调用字符串的length()方法获取它的长度,使用charAt(index)方法从字符串中提取特定下标位置的字符,使用indexOf和lastIndexOf方法找出一个字符串中的某个字符或某个子串。

11、可以使用concat方法连接两个字符串,或者使用加号(+)连接两个或多个字符串。

12、可以使用substring发给发从字符串中提取子串。

13、可以使用equals和compareTo方法比较字符串。如果两个字符串相等,equals方法返回true;如果它们不等,则返回false。compareTo方法根据一个字符串大于、等于或小于另一个字符串,分别返回0、正整数或负整数。

14、printf方法使用格式标识符来显示一个格式化的输出。





第5章 循环

5.1 引言

循环简介:

  • 循环可以用于让一个程序重复地执行语句。

  • Java提供了一种称为循环(loop)的功能强大的结构,用来控制一个操作或操作序列重复执行的次数。

  • 循环是用来控制语句块重复执行的一种结构。

  • 循环的概念是程序设计的基础。

  • Java提供了三种类型的循环语句:while循环、do-while循环和for循环。



5.2 while循环

while循环在条件为真的情况下,重复地执行语句。

语法格式:

while(循环继续条件){
    //循环体
    语句()}

循环中包含的重复执行的语句部分称为循环体(loop body)。循环体的每一次执行都被认为是一次循环的迭代(或重复)。每个循环都含有循环继续条件,循环继续条件是一个布尔表达式,控制循环体的执行。

在循环体执行前总是先计算循环条件以决定是否执行它。若条件为真,则执行循环体;若条件为false,则终止整个循环,并且程序控制转移到while循环后的下一条语句。

使用一个控制变量count来对执行次数计数。这种类型的循环称为计数器控制的循环(counter-controlled loop)。

注意:循环继续条件应该总是放在圆括号内。只有当循环体只包含一条语句或不包含语句时,循环体的花括号才可以省略。

**注意:**要保证循环继续条件最终可以变为false,以便程序能够结束。一个常见的程序设计错误是无限循环(也就是说,循环会永远执行下去)。如果程序运行了不寻常的长时间而不结束,可能其中有无限循环。如果你是从命令行窗口运行程序的,按Ctrl+C键来结束。

**警告:**程序员常犯的错误就是使循环多执行一次或少执行一次。这种情况通常称为差一错误(off-by-one error)。

//提示用户为两个个位数相加的问题给出答案。使用循环,让用户重复输入新的答案,直到答案正确为止。
import java.util.Scanenr;

public class RepeatAdditionQuiz{
    public static void main(String[] args){
        int number1 = (int)(Math.random()*10);
        int number2 = (int)(Math.random()*10);
        
        //Create a Scanenr
        Scanner input = new Scanner(System.in);
        
        System.out.print("What is " + number1 + " + " + numnber2 + " ? ");
        int answer = input.nextInt();
        
        while(number1 + number2 != answer){
            System.out.print("Wrong answer.Try again.What is " + number1 + " + " + number2 + " ? ");
            answer = input.nextInt();
        }
        
        System.out.println("You got it!");
    }
}

5.2.1 示例学习:猜数字

一次增加一个步骤地渐进编码(code incrementally)是一个很好地习惯。对涉及编写循环地程序而言,如果不知道如何立即编写循环,可以编写循环只执行一次的代码,然后规划如何在循环中重复执行这些代码。

import java.util.Scanner;

/**
 * 程序清单5-2
 * 
 * 要解决的问题是猜测计算机“脑子”里想的是什么数。编写一个程序,随机产生一个0到100之间的且包含0和100的整数。程序提示用户连续输入一个数字,直到它和计算机随机产生的数字相匹配为止。对用户每次输入的数字,程序都要告诉用户该输入值是偏大了,还是偏小了,这样用户可以明智地进行下一轮的猜测。
 * 
 * 这个魔法数在0到100之间。为了减小猜测的次数,首先输入50,如果猜测值过高,那么这个魔法数就在0到49之间。如果猜测值过低,那么这个魔法数就在51到100之间。因此,经过一次猜测之后,下一次猜测时可以少考虑一半的数字。
 * 
 * 该如何编写编写这个程序呢?要立即开始编写吗?不!编码前的思考是非常重要的。思考一下,在没有编写程序时你会如何解决这个问题。首先需要产生一个0到100之间且包含0和100的随机数,然后提示用户输入一个猜测数,最后将这个猜测数和随机数进行比较。
 * 
 * 一次增加一个步骤地渐进编码(code incrementally)是一个很好地习惯。对涉及编写循环地程序而言,如果不知道如何立即编写循环,可以编写循环只执行一次的代码,然后规划如何在循环中重复执行这些代码。
 */
public class GuessNumber {
    public static void main(String[] args) {
        //Generate a random number to be guessed
        int number = (int) (Math.random() * 101); //小于1.0 * 101 那么得到的值<101,对它进行取整,最大会得到100

        Scanner input = new Scanner(System.in);
        System.out.println("Guess a magic number between 0 and 100");

        int guess = -1; //注意:guess被初始化为-1,i昂他初始化为0到100之间的值会出错,因为它很可能就是要猜的数字。
        while (guess != number) {
            //Prompt the user to guess the number
            System.out.print("\nEnter your guess:");
            guess = input.nextInt();


            if (guess == number)
                System.out.println("Yes,the number is " + number);
            else if (guess > number)
                System.out.println("Your guess is too hight.");
            else
                System.out.println("Your guess is too low");
        }
    }
}

5.2.2 循环设计策略

编写一个正确的循环对编程新手来说,并不是件容易的事。编写循环时应该考虑如下三个步骤:

  • 第一步:确定需要重复的语句。
  • 第二步:将这些语句放在一个循环中。
  • 第三步:为循环继续条件编码,并为控制循环添加合适的语句。

5.2.3 示例学习:多个减法测试题
import java.util.Scanner;

/**
 *
 * 程序清单5-4
 *
 * 程序清单3-3中的数学减法学习工具程序,每次运行只能产生一道题目。可以使用一个循环重复产生题目。那么如何编写能产生5道题目的代码呢?遵循循环设计策略。首先,确定需要重复的语句。这些语句包括:获取两个随机数,提示用户对两数做减法然后给试题打分。然后,将这些语句放在一个循环里。最后,增加一个循环控制变量和循环继续条件,然后执行循环五次。
 *
 * 给出程序可以产生5道问题,在学生回答完所有5个问题后,报告回答正确的题数。这个程序还显示该测试所花的时间,并列出所有的题目。
 */
public class SubtractionQuizLoop {
    public static void main(String[] args) {

        final int NUMBER_OF_QUESTIONS = 5; //Number of questions

        int correctCount = 0; //Count the number of corredect answers
        int count = 0; //Count the number of questions
        long startTime = System.currentTimeMillis();
        String output = " "; //output string is initially empty

        Scanner input = new Scanner(System.in);



        while (count < NUMBER_OF_QUESTIONS) {

//        1.Generate two random single-digit integers
            int number1 = (int) (Math.random() * 10);
            int number2 = (int) (Math.random() * 10);

            //2.If number < number2,swap number1 with number2
            if (number1 < number2) {
                int temp = number1;
                number1 = number2;
                number2 = temp;
            }

            //3.Prompt the student to answer "What is number1 - number2?"
            System.out.print("What is " + number1 + " - " + number2 + " ? ");
            int answer = input.nextInt();

            //4.Grade the answer and display the result
            if (number1 - number2 == answer) {
                System.out.println("You are correct!");
                correctCount++; //Increase the correct answer count
            } else
                System.out.println("Your answer is wrong.\n" + number1 + " - " + number2 + " should be " + (number1 - number2));

            //Increase the question count
            count++;

            output += "\n" + number1 + "-" + number2 + "=" + answer + ((number1 - number2 == answer) ? " correnct" : " wrong");

        }

        long endTime = System.currentTimeMillis();
        long testTime = endTime - startTime;

        System.out.println("\nCorrect count is " + correctCount + "\nTest time is " + testTime / 1000 + " seconds\n" + output);
    }

}

5.2.4 使用标记值控制循环

另一种控制循环的常用技术是读取和处理一个集合的值时指派一个特殊值。这个特殊的输入值也称为标记值(sentinel value),用以表明循环的结束。如果一个循环使用标记值来控制它的执行,它就被称为标记位控制的循环(sentinel-controlled loop)。

警告:在循环控制中,不要使用浮点值来比较值是否相等。因为浮点值都是某些值的近似值,使用它们可能导致不精确的循环次数和不准确的结果。

double item = 1;double sum = 0;
while(item != 0){ // No guarantee item will be 0
    sum += item;
    item -= 0.1;
}

/*
变量item从1开始,每执行一次循环体就减去0.1.当item变为0时循环应该终止。但是,因为浮点数在算数上是近似的,所以不能确保item会变成真正的0.从表面上看,这个循环似乎没问题,但实际上它是以无限循环。
*/

5.2.5 输入和输出重定向
  • 输入重定向命令(input redirection):

    • java SentinelValue < input.txt
    • 输入重定向让程序从文件中读取输入,而不是让用户在运行时从键盘输入数据。
  • 输出重定向命令(output redirection):

    • java ClassName > output.txt
    • 输出重定向让程序输出发送给文件,而不是将它们显示在控制台上。
  • 同时使用输入/输出重定向:

    • java ClassName output.txt


5.3 do-while循环

do-while循环和while循环基本一样,不同的是它先执行循环体一次,然后判断循环继续条件。

do-while循环是while循环的变体。

语法格式:

do{
    //循环体;
    语句()}while(循环继续条件);

首先执行循环体,然后计算循环继续条件。如果计算结果为true,则重复执行循环体;如果为false,则终止do-while循环。

while循环与do-while循环的差别在于:计算循环继续条件和执行循环体的先后顺序不同。

//TestDoWhile.java
import java.util.Scanner;

public class TestDoWhile{
    //Main method
    public static void main(String[] args){
        int data;
        int sum = 0;
        
        //Create a Scanner 
        Scanner input = new Scanner(System.in);
        
        //Keep reading data util the input is 0
        do{
            //Read the next data
            System.out.print("Enter an integer(the input ends if it is 0): ");
            data = input.nextInt();
            
            sum += data;
        }while(data != 0);
        
        System.out.println("The sum is " + sum);
    }
}

**提示:**如果循环中的语句至少需要执行一次,建议使用do-while循环。如果使用while循环,那么这些语句必须在循环前和循环内都出现。



5.4 for循环

for循环具有编写循环的简明语法。

for循环的语法格式:

for(初始操作;循环继续条件;每次迭代后的操作){
    //循环体
    语句(组);
}

for循环语句从关键字for开始,然后是用括号括住的循环控制结构体。这个结构体包括初始动作、循环继续条件和每次迭代后代的动作。控制结构体后紧跟着花括号括起来的循环体。初始动作、循环继续条件和每次迭代后的动作都要用分号分隔。

一般情况下,for循环使用一个变量来控制循环体的执行次数,以及什么时候循环终止。这个变量称为控制变量(control variable)。初始化动作是指初始化控制变量,每次迭代后的动作通常会对控制变量做自增或自减,而循环继续条件检验控制便来给你是否达到终止值。

循环继续条件是一个布尔表达式。这个表达式在初始化之后和每次迭代开始之前都要计算一次。如果条件为true,则执行循环体。如果它为false,则循环终止,并且将程序控制转移到循环后的下一行。

最终,控制变量的值应该使循环继续条件变为false,否则循环将成为无限循环。

循环体内只有一条语句,则可以省略花括号。这个可以省略花括号的底层实现是怎样的?

**提示:**控制变量必须在循环控制结构体内或循环前说明。如果循环控制变量只在循环内使用而不再其他地方使用,那么在for循环的初始动作中声明它是一个很好的编程习惯。如果在循环控制结构体内声明变量,那么在循环外不能引用它。

**注意:**for循环中的初始动作可以是0个或是多个以逗号隔开的变量声明语句或赋值表达式。

for循环中每次迭代后的动作可以是0个多个以逗号隔开的语句。

for(int i = 0;i<100;System.out.println(i),i++)

通常,将声明和初始化/一个变量/作为初始动作,将增加或减少控制变量作为每次迭代后的操作。

**注意:**如果省略for循环中的循环继续条件,则隐含地认为循环继续条件为true。



5.5 采用哪种循环

while循环和for循环都称为前测循环(pretest loop),因为继续条件实在循环体执行之前检测的,do-while循环称为后测循环(posttest loop),因为循环条件是在循环体执行之后检测的。

三种形式的循环语句:while、do-while和for,在表达上是等价的。也就是说,可以使用这三种形式之一来编写一个循环。除了某些特殊情况外。

for(initial-action;loop-continuation-condition;action-after-each-iteration){
    //Loop body;
}

//==================等价于

initial-action;
while(loop-continuation-condition){
    //Loop body;
    action-after-each-iteration;
}

通常,如果已经提前知道重复次数,那就采用for循环。如果无法确定重复次数,就采用while循环,就像读入一些数值直到读入0为止的这种情况。如果在检验继续条件前需要执行循环体,就用do-while循环替换while循环。

在for子句的末尾和循环体之间多写分号是一个常见的错误。循环体实际上都是为空的。

在do-while循环中,需要分号来结束这个循环。



5.6 嵌套循环

一个循环可以嵌套在另外一个循环中。

嵌套循环是由一个外层循环和一个或多个内层循环组成的。每当重复执行一次外层循环时将再次进入内部循环,然后重新开始。

需要注意的是,嵌套循环将运行较长时间。



5.7 最小化数值错误

**要点提示:**在循环继续条件中使用浮点数将导致数值错误。

涉及浮点数的数值误差是不可避免的,因为浮点数在计算机中本身就是近似表示的浮点数本身是如何存储在计算机中的???。

for循环初始动作可以是任何语句,但是,它经常用来初始化控制变量。事实上,它可以为任意数据类型

因为计算机使用固定位数表示浮点数,因此,它就不能精确表示某些浮点数。

使用浮点数做计数器会导致最后一次循环不会运行。根本问题就是浮点数使用近似值表示的。为了解决这个问题,使用整数计数器以确保所有循环次数都会执行。

double currentValue = 1.0;
for(int count = 0;count<100;count++){
	sum += currentValue;
	currentValue -= 0.01;
}

从大到小添加数字没有从小到大添加数字得到的值精确。这种现象是有限精度算数的产物。如果结果值要求的精度比便来给你可以存储的更高,那么添加一个非常小的数到一个非常大的数上可能没有什么影响。为了得到更精确的结果,仔细选择计算的顺序。在较大数之前先增加较小数是减小误差的一种方法。



5.8 示例学习

5.8.1 求最大公约数

在编写代码之前进行思考是非常重要的。思考让你可以在考虑如何编写代码前,生成解决问题的逻辑方案。

将逻辑方案翻译成Java代码的方式不是唯一的。

一个问题常常有多种解决方案。

最大公约数(GCD)问题就有许多解决方法。一个更有效的是使用经典的欧几里得算法。

import java.util.Scanner;

/**
 *
 * 程序清单5-9
 *
 * 提示用户输入两个正整数,然后找到它们的最大公约数
 */
public class GreatestCommonDivisor {

    public static void main(String[] args) {

        //Create a Scanner object
        Scanner input = new Scanner(System.in);

        //Prompt the user to enter two integer
        System.out.print("Enter first integer: ");
        int n1 = input.nextInt();
        System.out.print("Enter second integer: ");
        int n2 = input.nextInt();

        int gcd = 1; //Initial gcd is 1 //Initial 初始
        int k = 2;  //Possible gcd
        while(k <= n1 && k <= n2){
            if(n1 % k ==0 && n2 % k ==0)
                gcd = k; //Update gcd
            k++;
        }
        System.out.println("The greatest common divisor for " + n1 + " and " + n2 + " is " + gcd);
        
        /*
        考虑到n1的除数不可能大于n1/2,所以,可以改写:
        for(int k = 2;k <= n1/2&&k<=n2/2;k++){
        if(n1%k ==0 && n2%k ==0)
        	gcd = k;
        }
        */
    }
}

5.8.2 预测未来学费
public class FutureTuition {
    public static void main(String[] args) {
        double tuition = 10000; //Year 0
        int year = 0;
        while(tuition < 20000){
            tuition = tuition * 1.07;
            year++;
        }

        System.out.println("Tuition will be doubled in " + year + " years");
        System.out.printf("Tuition will be $%.2f in %1d years",tuition,year);
    }
}

5.8.3 将十进制数转换为十六进制数

将十进制数转换为十六进制数,就是找到满足以下条件的十六进制数hn,hn-1,hn-2,…h2,h1,h0:这些数可以通过不断地用d除以16直到商为零而得到。依次得到的余数是hn,hn-1,hn-2,…h2,h1,h0。十六进制数字包含十进制数字0、1、2、3、4、5、6、7、8、9以及表示十进制数字的10的A,表示十进制数字11的B,表示12的C,13的D、14的E和表示15的F。

​ d = hnx16n + hn-1x16n-1 + hn-2x16n-2,…h2x162 + h1x161 + h0x160

import java.util.Scanner;
/**
 * 程序清单5-11
 *
 * 提示用户输入一个十进制数,然后将它转换为一个字符串形式的十六进制数
 *
 */
public class Dec2Hex {
    //Main method
    public static void main(String[] args) {
        //Create a Scanenr object
        Scanner input = new Scanner(System.in);

        //Prompt the user to enter a decimal integer
        System.out.print("Enter a decimal number: ");
        int decimal = input.nextInt();

        //Covert decimal to hex
        String hex = "";

        while(decimal != 0){
            int hexValue = decimal % 16;

            //Convert a decimal value to a hex digit
            char hexDigit = (hexValue <= 9 && hexValue >=0) ? (char)(hexValue + '0') : (char)(hexValue - 10 + 'A');

            hex = hexDigit + hex;
            decimal = decimal / 16;
        }

        System.out.println("The hex  number is " + hex);

		/*
		程序提示用户输入一个十进制数字,将其转换为一个十六进制形式的字符串,然后显示结果。为了将十进制转换为十六进制数,程序运行循环不断地将十进制数除以16,得到其余数。余数转换为一个十六进制形式地字符串。接下来,这个表示十六进制数的字符串初始时为空。将这个十进制数除以16,就从该数中去掉一个十六进制数字。循环重复执行这些操作,直到商是0为止。
		程序将0到15之间的十六进制数转换为一个十六进制字符。如果hexValue在0到9之间,那它就被转换为(char)(hexValue+'0')。回顾一下,当一个字符和一个整数相加时,计算时使用的是字符的Unicode码。利用:如果hexValue为5,那么(char)(hexValue+'0')返回5.类似地,如果hexValue在10到15之间,那么它就被转换为(char)(hexValue-10+'A')。例如,如果hexValue是11,那么(char)(hexValue-10+'A')返回B。

    }
}


5.9 关键字break和continue

关键字break和continue在循环中提供了额外的控制。

关键字break和continue都可以在循环语句中使用。为循环提供额外的控制。在某些情况下,使用break和continue可以简化程序设计。但是过度使用或者不正确地使用它们会使得程序难以调试。

可以在一个循环中使用break立即终止该循环。

也可以在循环中使用关键字continue。当程序遇到continue时,它会结束当前地迭代。程序控制转向该循环体的末尾。换句话说,continue只是跳出了一次迭代,而关键字break是跳出了整个循环。

注意:continue语句总是在一个循环内。在while和do-while循环中,continue语句之后会马上计算循环继续条件;而在for循环中,continue语句之后会立即先执行每次迭代后的动作,再计算循环继续条件。

总是可以编写在循环中不使用break和continue的程序。通常,只有在能够简化代码并使程序更容易阅读的情况下,才适合使用break和continue。

**注意:**很多程序设计语言都有goto语句。goto语句可以随意地将控制转移到程序中地任意一条语句上,然后执行它。这使程序很容易出错。Java中地break语句和continue语句是不同于goto语句地。它们只能运行在循环中或者switch语句中。break语句跳出整个循环,而continue语句跳出循环的当前迭代。



5.10 示例学习:判断回文串

如果一个字符串从前往后,以及从后往前是一样的,那么它就是一个回文。

import java.util.Scanner;
/**
 * 程序清单5-14
 *
 * 要解决的问题是:编写一个程序,提示用户输入一个字符串,然后给出该字符串是否是回文。一个解决方案是,判断字符串的第一个字符是否和最后一个字符一样。如果是,判断第二个字符是否和倒数第二个字符一样。这个过程一直持续到找到不匹配的,或者字符串中所有的字符都进行了判断。如果字符串具有奇数个数,那么中间的字符就不需要判断了。
 *
 *
 */
public class Palindrome {

    //Main method
    public static void main(String[] args) {
        //Create a Scanner object
        Scanner input = new Scanner(System.in);

        //Prompt the user to enter a String
        System.out.println("Enter a string: ");
        String s = input.nextLine();

        //The index of the first character in the string
        int low = 0;
        
        //The index of the last character in the string
        int high = s.length()-1;
        
        boolean isPalindrome = true;
        while(low<high){
            if(s.charAt(low) != s.charAt(high)){
                isPalindrome = false;
                break;
            }
            
            low++;
            high--;
        }
        
        if(isPalindrome)
            System.out.println(s + " is a palindrome.");
        else
            System.out.println(s + " is not a palindromem.");

    }
}


5.11 示例学习:显示素数

开发编程解决方案来解决这个问题或其他很多问题的关键之处在于要把问题分解成子问题,然后逐个地开发出每个子问题的解决方案。不要一开始就试图开发出一个完整的解决方案。

/**
 * 本节给出了一个程序,用于分5行显示前50个素数,每行包含10个数字。
 */
public class PrimeNumber {
    public static void main(String[] args) {
        final int NUMBER_OF_PRIMES = 50; //Number of primes to dispaly
        final int NUMBER_OF_PRIMES_PER_LINE = 10; //Display 10 per line
        int count = 0; // Count the number of prime numbers
        int number = 2; // A number to be teseted for primeness

        System.out.println("The first 50 prime numbers are \n");

        //Repeatedly find prime numbers
        while(count<NUMBER_OF_PRIMES){
            //Assume the number is prime
            boolean isPrime = true;//Is the current number prime?

            //Test whether nummber is prime
            for(int divisor = 2;divisor<= number/2 ;divisor++){
                if(number % divisor ==0){ // If true,number if not prime
                    isPrime = false; // Set is Prime to false
                    break;

                }
            }

            //Display the prime number and increase the count
            if(isPrime){
                count++; //Increase the count

                if(count%NUMBER_OF_PRIMES_PER_LINE == 0){
                    //Display the number and advance to the new line
                    System.out.println(number);
                }else
                    System.out.print(number + " ");
            }

            //Check if the number is prime
            number ++;
        }
    }
}


本章小结

1、循环语句有三类:while循环、do-while循环和for循环。

2、循环中包含重复执行的语句的部分称为循环体。

3、循环体执行一次称为村换的一次迭代。

4、无限循环是指循环语句被无限次执行。

5、在设计循环时,既需要考虑循环控制结构,还需要考虑循环体。

6、while循环首先检查循环继续条件。如果条件为true,则执行循环体;如果条件为false,则循环结束。

7、do-while循环与while循环类似,只是do-while循环先执行循环体,然后再检查循环继续条件,以确定是继续还是终止。

8、while和do-while循环常用于循环次数不确定的情况。

9、标记值是一个特殊的值,用来标记循环的结束。

10、for循环一般用在循环体执行次数固定的情况。

11、for循环控制由三部分组成。第一部分是初始操作,通常用于初始化控制变量。第二部分是循环继续条件,决定是否执行循环体。第三部分是每次迭代后执行的操作,经常用于调整控制变量。通常,再控制结构中初始化和修改循环控制变量。

12、while循环和for循环都称为前测循环(pretest loop),因为在循环体执行之前,要检测一下循环继续条件。

13、do-while循环称为后测循环(posttest loop),因为在循环体执行之后,要检测一下这个条件。

14、在循环中可以使用break和continue这两个关键字。

15、关键字break立即终止包含break的最内层循环。

16、关键字continue只是终止当前迭代。





第6章 方法

6.1 引言

要点提示:方法可以用于定义可重用的代码以及组织和简化编码。

可以通过定义和调用方法实现一次性地编写好通用的代码而无须重新编写。

方法是为完成一个操作而组合在一起的语句组。

方法:具有独立功能的代码块,不调用就不执行。

预定义的方法都在Java库中定义。

注意事项:

  • 1、方法和方法是平级关系,不能嵌套定义。
  • 2、方法不调用就不执行。
  • 3、方法的定义顺序和执行顺序无关,和调用顺序有关。

示例代码:

/**
  * 需求:编写计算从i1到i2的和的方法
  *
  */
// 和计算方法
public static int sum(int i1, int i2){
    int result = 0;
    for(int i = i1; i <= i2; i++){
        result += i;
    }
    //返回结果值
    return result;
}
// 调用方法
public static void main(String[] args){
    System.out.println("Sum from 1 to 10 is: " + sum(1, 10));
    System.out.println("Sum from 20 to 37 is: " + sum(20, 37));
    System.out.println("Sum from 35 to 49 is: " + sum(35, 49));
    
}


6.2 定义方法

要点提示:方法的定义由方法名称、参数、返回值类型以及方法体组成。

语法格式:

修饰符  返回值类型  方法名(参数列表){
    //方法体;
}

方法头(method header)是指方法的修饰符(modifier)、返回值类型(return value type)、方法名(method name)和方法的参数(parameter)。

示例:求两个整数中的最大值

//需求 找出两个整数中那个数比较大
//方法头(method header)= 方法的修饰符(modifier)+ 返回值类型(return value type) + 方法名(method name) + 方法的参数(parameter)
//修饰符:public static
//返回值类型:int
//方法名:max
//方法签名:max(int num1, int num2)
//参数列表:int num1,int num2
//形式参数:num1 num2
public static int max(int num1, int num2){
    
    //方法体
    int result;
   	
    //算法一:result = num1 > num2 ? num1 :num2;
	
    //算法二:
    if(num1 > num2){
        result = num1;
    }else{
        result = num2;
    }
   
    //返回值
    return result;
    
}

public static void main(String args[]){
    //实际参数:x,y
    int z = max(x,y);
}

带返回值的方法(value-returning method):

  • 方法可以返回一个值。returnValueType是方法返回值的数据类型。

void方法(void method):

  • 有些方法只是完成某些要求的操作,而不返回值。在这种情况下,returnValueType为关键字void。main、System.exit、System.out.println方法中的返回值类型都是void。

定义在方法头中的变量称为形式参数(formal parmeter)或者简称为形参(parameter)。

参数就像占位符,当调用方法时,就给参数传递一个值,这个值称为实际参数(actual parameter)或实参(argument)。

参数列表(parameter list)指明方法中参数的类型顺序个数

方法名和参数列表一起构成方法签名(method signature)。

参数是可选的,也就是说,方法可以不包含参数。

方法体中包含一个执行方法的语句集合。

为使带返回值的方法能返回一个结果,必须要使用带关键字return的返回语句。

执行return语句时方法终止。

注意:在其他某些语言中,方法称为过程(procedure)或函数(function)。带返回值的方法称为函数,返回值类型为void的方法称为过程。

**警告:**在方法头中,需要对每一个参数进行独立的数据类型声明。

注意:定义方法和声明变量的区别:定义是指被定义的条目是什么,而声明通常是指被声明的条目分配内存来存储数据。



6.3 调用方法

**要点提示:**方法的调用时执行方法中的代码。

在方法定义中,定义方法要做什么。

为了使用方法,必须调用(call或invoke)它。

根据方法是否有返回值,调用方法有两种途径:

  • 如果方法返回一个值,对方法的调用通常就当作一个值处理.
    • int larger = max(3,4);
  • 如果方法返回void,对方法的调用必须是一条语句。
    • System.out.println("Welcome to java!")

**注意:**在Java中,带返回值的方法也可以当作语句调用。这种情况下,函数调用者只需忽略返回值即可。虽然这种情况很少见,但是,如果调用者对返回值不感兴趣,这样也是可以的。

当程序调用一个方法时,程序控制就转移到被调用的方法。当执行完return语句或执行到表示方法结束的右括号时,被调用的方法将程序控制返还给调用者。

方法的调用流程——内存图讲解:

  • 方法没有被调用的时候,在方法区中的字节码文件中存放
  • 方法被调用的时候,需要进入到栈内存中运行
  • 方法执行完毕,弹栈消失。
//测试max方法的完整程序
public class TestMax{
    // Main method
    public static void main(String args[]){
        int i = 5;
        int j = 2;
        int k = max(i, j);
        System.out.println("The maximum of " + i + " and " + j + " is " + k);
    }
    
    //Return the max of two numbers
    public static int max(int num1, int num2){
        int result;
        
        if(num1 > num2)
            result = num1;
        else
            result = num2;
    }
}

main方法:

  • main方法与其他方法很类似,区别在于它是由Java虚拟机调用的。

  • main方法的方法头永远都是一样的。

  • main方法方法头解读:修饰符public和static,返回值类型void,方法名main,String[]类型的参数。String[]表明参数是一个String类型的数组。

  • main中的语句可以调用main方法所在类中定义的其他方法,也可以调用别的类中定义的方法。

**警告:**对带返回值的方法而言,return语句是必需的。

如果方法的返回值类型不是void,必须使用return语句返回结果,编译器没有那么智能,编译器认为只要return语句被条件所控制,就有可能执行不到。

//编译错误,Java编译器认为该方法有可能不会返回任何值。???????????’
//如果方法的返回值类型不是void,必须使用return语句返回结果,编译器没有那么智能,编译器认为只要return语句被条件所控制,就有可能执行不到。
public static int sign(int n){
    if(n > 0)
        return 1;
    else if(n == 0)
        return 0;
    else if(n < 0);
    	return -1;
}

//正确代码
public static int sign(int n){
    if(n > 0)
        return 1;
    else if(n == 0)
        return 0;
    else    //删除此处的判断,使得总是有语句可以被执行。
    	return -1;
}

**注意:**方法能够带来代码的共享和重用。

如果创建了一个新类,可以通过使用“类名.方法名”来调用它。

每当调用一个方法时,系统会创建一个活动记录(也称为活动框架),用于保存方法中的参数和变量。活动记录置于内存区域中,称为调用堆栈(call statck)。调用堆栈也称为执行堆栈、运行时堆栈,或者一个机器堆栈,常简称为“堆栈”。当一个方法调用另一个方法时,调用者的活动记录保持不动,一个新的活动记录被创建用于被调用的新方法。一个方法结束返回到调用者时,其相应的活动记录也被释放。

方法的值通过方法调用进行传递。



6.4 void方法示例

**要点提示:**void方法不返回值。

对void方法的调用必须是一条语句。

//案例:打印分数的void方法
//对void方法的调用必须是一条语句。
public class TestVoidMehod{
    public static void main(String[] args){
        System.out.println("The grade is ");
        printGrade(78.5);
        
        System.out.println("The grade is ");
        printGrade(59.5);
    }
    
    public static void printGrade(double score){
        if(score >= 90.0)
            System.out.println('A');
        else if(score >= 80.0)
            System.out.println('B');
        else if(score >= 70.0)
            System.out.println('C');
        else if(score >= 60.0)
            System.out.println('D');
        else
            System.out.println('F');
    }
}
//带返回值的getGrade方法
public class TestReturnGradeMethod{
    public static void main(String args[]){
        System.out.print("The grade is " + getGrade(78.5));
        System.out.println("\nThe grade is " + getGrade(59.5));
    }
    
    public static char getGrade(double score){
        if(score >= 90.0){
            retun 'A';
        }
        else if(score >= 80.0){
            return 'B';
        }
        else if(score >= 70.0){
            return 'C';
        }
        else if(score >= 60.0){
            return 'D';
        }
        else{
            return 'F';
        }
    }
}

注意:void方法不需要return语句,但它能用于终止方法并返回到方法的调用者。它的语法是:return;。

return和break和continue的区别:

  • return是跳出方法
  • 而break和continue则是跳出循环。


6.5 通过传值进行参数传递

**要点提示:**调用方法的时候是通过传值的方式将实参传给形参的。

形参和实参:

  • 形参:全称形式参数,是指在定义方法时,所声明的参数。
  • 实参:全称实际参数,调用方法时,所传入的参数。

方法的强大之处在于它处理参数的能力。

调用方法时,需要提供实参,它们必须与方法签名中所对应的形参次序相同。这称作参数顺序匹配(parameter order association)。

警告:实参必须与方法签名中定义的参数在次序和数量上匹配,在类型上兼容。类型兼容是指不需要经过显式的类型转换,实参的值就可以传递给形参。

当调用带参数的方法时,实参的值传递给形参,这个过程称为按值传递(pass-by-value)。

如果实参是变量而不是常量,则将该变量的值传递给形参。无论形参在方法中是否改变,该变量都不受影响。这儿的意思是说,实参传给形参的是一个值,而非地址,所以不会改变实参原来的值。

带参数的方法,本质上是在方法运行时,在栈内存该方法中创建了一个存放该参数的变量。

//实现交换两个变量的swap方法
public class TestPassByValue{
    //Main Method
    public static void main(String[] args){
        //Declare and initialize variables
        int num1 = 1;
        int num2 = 2;
        
        System.out.println("Before invoking the swap method,num1 is " + num1 + " and num2 is " + num2);
        
        //Invoke the swap method to attempt to swap two variables 
        swap(num1, num2);
        
        System.out.println("After invoking the swap method,num1 is " + num1 + " and num2 is " + num2);
    }
    
    //Swap two variables
    public static void swap(int n1, int n2){
        System.out.println("\tInside the swap method");
        System.out.println("\t\tBefore swapping, n1 is " + n1 + " and n2 is " + n2);
        
        //Swap n1 with n2
        int temp = n1;
        n1 = n2;
        n2 = temp;
        
        System.out.println("\t\tAfter swapping,n1 is " + n1 + " and n2 is " + n2);
    }
}

形参和实参同名是没有任何分别的。形参是方法中具有自己存储空间的变量。局部变量是在调用方法时分配的,当方法返回到调用者后它就消失了。



6.6 模块化代码

**要点提示:**模块化使得代码易于维护和调试,并且使得代码可以被重用。

使用方法可以减少冗余的代码,提高代码的复用性。方法也可以用来模块化代码,以提高程序的质量。

//提示用户输入两个整数,然后显示它们的最大公约数。
import java.util.Scanner;

public class GreatestCommonDivisorMethod{
    //Main method
    public static void main(String[] args){
        //Create a Scanner
        Scanner input = new Scanner(System.in);
        
        //Prompt the user to enter two integers
        System.out.print("Enter first integer:");
        int n1 = input.nextInt();
        System.out.print("Enter second integer:");
        int n2 = input.nextInt();
        
        System.out.println("The greatest common divisor for " + n1 + " and " + n2 + " is " +gcd(n1,n2));
    }
    
    //Return the gcd of two integers
    public static int gcd(int n1, int n2){
        int gcd = 1; //Initial gcd is 1
        int k = 2; //Possible gcd
        
        while(k <= n1 && k <= n2){
            if(n1 % k == 0 && n2 % k == 0){
                gcd = k; // Update gcd
            }
            k++;
        }
        
        return gcd; //Return gcd
    }
}

/*
 *这个程序的3个优点:
 *1、它将计算最大公约数的问题和main方法中的其他代码分隔开,这样做会使逻辑更清晰而且程序的可读性更强
 *2、计算最大公约数的错误就限定在gcd方法中,这样就缩小了调试的范围。
 *3、现在, 其他程序就可以重复调用gcd方法。
 */
//使用你模块化的概念来对打印前50的素数,每行包含10个数字进行改进
public class PrimeNumberMethod {
    public static void main(String[] args) {
        System.out.println("The first 50 prime numbers ars \n");
        printPrimeNumbers(50);
    }

    public static void printPrimeNumbers(int numberOfPrimes) {
        final int NUMBER_OF_PRIMES_PER_LINE = 10; //Display 10 per line
        int count = 0; //Count the number of prime numbers
        int number = 2; //A number to be tested for primeness

        //Repeatedly find prime numbers
        while (count < numberOfPrimes) {
            //Print the prime number and increase the count
            if (isPrime(number)) {
                count++; //Increase the count
                if (count % NUMBER_OF_PRIMES_PER_LINE == 0) {
                    //Print the number and advance to the new line
                    System.out.printf("%-5s\n", number);
                } else
                    System.out.printf("%-5s", number);
            }

            //Check whether the next number is prime
            number++;
        }
    }

    //Check  whether number is prime
    public static boolean isPrime(int number) {
        for (int divisor = 2; divisor <= number / 2; divisor++) {
            if (number % divisor == 0) //If true,number is not prime
                return false; //number is not a prime
        }
        return true; //Number is prime
    }
}


6.7 示例学习:将十六进制数转换为十进制数

霍纳算法:

import java.util.Scanner;

/**
 * 程序清单6-8
 *
 * 使用霍纳算法,将十六进制字符串转换为十进制数的高效算法
 *
 */
public class Hex2Dec {
    //Main method
    public static void main(String[] args) {
        //Create a Scanner object
        Scanner input = new Scanner(System.in);
        
        //Prompt the user to enter a String
        System.out.print("Enter a string: ");
        String hex = input.nextLine();
        
        System.out.println("The dicimal value for hex number " + hex + " is " + hexToDecimal(hex.toUpperCase()));
    }
    
    public static int hexToDecimal(String hex){
        int decimalValue = 0;
        for(int i = 0;i < hex.length();i++){
            char hexChar = hex.charAt(i);
            decimalValue = decimalValue * 16 + hexCharToDecimal(hexChar);
        }
        
        return decimalValue;
    }
    
    public static int hexCharToDecimal(char ch){
        if(ch >= 'A' && ch <= 'F')
            return 10 + ch - 'A';
        else //ch is '0','1',...,or '9'
            return ch - '0';
    }
}


6.8 重载方法

**要点提示:**重载方法使得你可以使用同样的名字来定义不同方法,只要它们的签名是不同的。(方法签名包含方法名,所以此处并不正确,应该是参数列表不同。)

方法重载:在一个类中有两个方法,它们具有相同的名字,但有不同的参数列表。Java编译器根据方法签名决定使用哪个方法。

调用方法时,Java编译器寻找最精确匹配的方法。

**提示:**重载方法可以使得程序更加清楚,以及更加具有可读性。执行同样功能但是具有不同参数类型的方法应该使用同样的名字。

注意:被重载的方法必须具有不同的参数列表。不能基于不同修饰符或返回值类型重载方法。

**注意:**有时调用一个方法时,会有两个或更多可能的匹配,但是,编译器无法判断那个是最精确的匹配,这称为歧义调用(ambiguous invocation)。歧义调用会产生一个编译错误。

/* 歧义调用 */
public class AmbiguousOverloading{
    public static void main(String[] args){
        System.out.println(max(1,2));
    }
    
    public static double max(int num1,double num2){
        if(num1>num2)
            return num1;
        else
            return num2;
    }
    
    public static double max(double num1,double num2){
        if(num1>num2)
            return num1;
        else
            return num2;
    }
}


6.9 变量的作用域

**要点提示:**变量的作用域(scope of a variable)是指变量可以在程序中引用的范围。

在方法中定义的变量称为局部变量(local variable)。

局部变量的作用域从声明变量的地方开始,直到包含该变量的块结束为止。

局部变量都必须在使用之前进行声明和赋值。

参数实际上就是一个局部变量。一个方法的参数的作用域涵盖整个方法。

可以在一个方法中的不同块里声明同名的局部变量,但是,不能在嵌套块中或同一块中两次声明同一个局部变量。

**警告:**不要再块内声明一个变量然后企图在块外使用它。



6.10 示例学习:生成随机字符

**要点提示:**字符使用整数来编码。产生一个随机字符就是产生一个随机整数。

每个字符都有一个唯一的在十六进制数0到FFFF(即十进制的65535)之间的Unicode。

注意:因为0<=Math.random()<1.0,必须给65535加上1.

小写字母的Unicode是一串连续的整数,从小写字母’a’的Unicode开始,然后是’b’、‘c’、…和’z’的Unicode。

(int)'a’到(int)'z’之间的随机整数是:

(int)((int)‘a’+Math.random()*((int)‘z’-(int)‘a’ +1)。

所有的数字操作符都可以应用到char操作数上。如果另一个操作数是数字或字符,那么char型操作数就会被转换为数字。

‘a’ + Math.random()*(‘z’-‘a’+1)

这样,随机的小写字母是:

(char)(‘a’+Math.random()*(‘z’-‘a’+1))

由此,可以生成任意两个字符ch1和ch2之间的随机字符,其中ch1

(char)(ch1+Math.random()*(ch2-ch1+1))

/**
 * 程序清单6-10
 *
 * 创建一个名为RandomCharacter的类,它有五个重载的方法,随机获取某种特性类型的字符。
 *
 */
public class RandomCharacter {
    /** Generate a random character between ch1 and ch2 */
    public static char getRandomCharacter(char ch1,char ch2){
        return (char)(ch1+Math.random()*(ch2-ch1+1));
    }

    /** Generate a random lowercase letter */
    public static char getRandomLowerCaseLetter(){
        return getRandomCharacter('a','z');
    }

    /** Generate a random uppercase letter */
    public static char getRandomUppercaseLetter(){
        return getRandomCharacter('A','Z');
    }

    /** Generate a random digit character */
    public static char getRandomDigitCharacter(){
        return getRandomCharacter('0','9');
    }

    /** Generate a random character */
    public static char getRandomCharacter(){
        return getRandomCharacter('\uu0000','\uFFFF');
    }
}



-----------------------------------------------------

/**
 * 程序清单6-11
 *
 * 显示175个随机的小写字母
 */
public class TestRandomCharacter {
    /** Main method */
    public static void main(String[] args){
        final int NUMBER_OF_CHARS = 175;
        final int CHARS_PER_LINE = 25;

        //Print random character between 'a' and 'z',25 chars per line
        for(int i = 0;i<NUMBER_OF_CHARS;i++){
            char ch = RandomCharacter.getRandomLowerCaseLetter();
            if((i+1) % CHARS_PER_LINE == 0)
                System.out.println(ch);
            else
                System.out.print(ch+ "\t");
        }
    }
}


6.11 方法抽象和逐步求精

要点提示:开发软件的关键在于应用抽象的概念。

方法抽象(method abstraction)是通过将方法的使用和它的实现分离来实现的。用户在不知道方法是如何实现的情况下,就可以使用方法。方法的实现细节封装在方法内,对使用该方法的用户来说是隐藏的。这就称为信息隐藏(information hiding)或封装(encapsulation)。如果决定改变方法的实现,但只要不改变方法签名,用户的程序就不受影响。

当编写一个大程序时,可以使用“分治”(divid-and-conquer)策略,也称为求精(stepwise refinement),将大问题分解成子问题。子问题又分解成更小、更容易处理的问题。

假设要编写一个程序,显示给定年月的日历。程序提示用户输入年份和月份,然后显示该月的整个日历。


6.11.1 自顶向下的设计

对日历问题来说,先把该问题拆分成两个子问题:读取用户输入和该月的日历。打印给定月份的日历还可以分解成两个子问题:打印日历的标题和日历的主体。打印日历主体时还可以分解成:获取该月的第一天是星期几和获取该月有多少天。


6.11.2 自顶向下和自底向上的实现

通常,一个子问题对应于现实中的一个方法,即使某些子问题太简单,以至于都不需要方法来实现。需要决定哪些模块要用方法实现,而哪些模块要与其他方法结合完成。这种决策应该基于所作的选择是否使整个程序更易阅读而做出的。

“自顶向下”方法是自上而下,每次实现结构图中的一个方法。等待实现的方法可以用待完善方法代替。

待完善方法(stub)是方法的一个简单但不完整的版本。使用待完善方法可以快速地构建程序的框架。

在每个待完善方法完成后,都需要编译和测试这个程序,然后修改所有的错误。

自底向上方法是从下向上每次实现结构图中的一个方法,对每个实现的方法都写一个测试程序进行测试。

自顶向下和自底向上都是不错的方法:它们都是逐步地实现方法,这有助于分离程序设计错误,使调试变得容易。

有时,这两种方法可以一起使用。


6.11.3 实现细节

实现打印日历的细节。

//打印日历
import java.util.Scanner;

/**
 * 程序清单6-12
 */
public class PrintCalendar {
    /**
     * Main method
     */
    public static void main(String[] args) {

        Scanner input = new Scanner(System.in);

        //Prompt the user to enter year
        System.out.print("Enter full year(e.g.,2012): ");
        int year = input.nextInt();

        //Prompt the user to enter month
        System.out.print("Enter month as a number between 1 and 12: ");
        int month = input.nextInt();


        //Print calendar for month of the year
        printMonth(year, month);

    }

    /**
     * Print the calendar for the month in a year
     */
    public static void printMonth(int year, int month) {
        //Print the headings of the calendar
        printMonthTitle(year, month);

        //Print the body of the calendar
        printMonthBody(year, month);
    }

    /**
     * Print the month title,e.g.,March 2012
     */
    public static void printMonthTitle(int year, int month) {
        System.out.println("        " + getMonthName(month) + " " + year);
        System.out.println("--------------------------");
        System.out.println(" Sun Mon Tue Wed Thu Fri Sat");
    }

    /**
     * Print month body
     */
    public static void printMonthBody(int year, int month) {
        //Get start day of the week for the first date in the month
        int startDay = getStartDay(year, month);

        //Get number of days in the month
        int numberOfDaysInMonth = getNumberOfDaysInMonth(year, month);

        //Pad space before the first day of the month
        int i = 0;
        for (i = 0; i < startDay; i++)
            System.out.print("    ");
        for(i=1;i<=numberOfDaysInMonth;i++){
            System.out.printf("%4d",i);

            if((i+startDay) % 7 ==0)
                System.out.println();
        }
        System.out.println();

    }

    /**
     * Get the English name for the month
     */
    public static String getMonthName(int month) {
//        return "January"; // A dummy value
        String monthName = "";
        switch (month) {
            case 1: monthName = "January";break;
            case 2: monthName = "february";break;
            case 3: monthName = "March"; break;
            case 4: monthName = "April"; break;
            case 5:monthName = "May"; break;
            case 6: monthName = "June";break;
            case 7: monthName = "July";break;
            case 8: monthName = "Auguts"; break;
            case 9:monthName = "September";break;
            case 10:monthName = "October"; break;
            case 11: monthName = "November"; break;
            case 12:monthName = "December";
        }
        return monthName;
    }

    /**
     * Get the start day of month/1/year
     */
    public static int getStartDay(int year, int month) {
        final int START_DAY_FOR_JAN_1_1800 = 3;

//        Get total number of days form 1/1/1800 to month/1/year
        int totalNumberOfDays = getTotalNumberOfDays(year,month);

        //Return the start day for month/1/year
        return (totalNumberOfDays + START_DAY_FOR_JAN_1_1800) % 7;
    }

    /**
     * Get the total number of days since January 1,1800
     */
    public static int getTotalNumberOfDays(int year, int month) {
        int total = 0;

        //Get the total days form 1800 to 1/1/Year
        for(int i = 1800;i<year;i++)
            if(isLeapYear(i))
                total += 366;
            else
                total += 365;

        //Add days form Jan to the month prior to the calendar month
        for(int i = 1;i<month;i++)
            total = total +getNumberOfDaysInMonth(year,i);

        return total;

    }

    /**
     * Get the number of days in a month
     */
    public static int getNumberOfDaysInMonth(int year, int month) {
        if(month == 1||month==3||month==5||month==7||month==8||month==10||month==12)
            return 31;

        if(month == 4||month ==6||month==9||month==11)
            return 30;

        if(month==2) return isLeapYear(year)?29:28;

        return 0; //If month is incorrect
    }

    /**
     * Determine if it is leap year
     */
    public static boolean isLeapYear(int year) {
        return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); //A dummy value
    }
}

6.11.4 逐步求精的优势

逐步求精将一个大问题分解为小的易于处理的子问题。每个子问题可以使用一个方法来实现。这种方法使得问题更加易于编写、重用、调试、修改和维护。

  • 1.更简单的程序:打印日历的程序比较长。逐步求精将其分解为较小的方法,而不是在一个方法中写很长的语句序列。这样简化了程序,使得整个程序易于阅读和理解。
  • 2.重用方法:逐步求精提高了一个程序中的方法重用。这减少了冗余的代码。
  • 3.易于开发、调试和测试:因为每个子问题在一个方法中解决,而一个方法可以分别的开发、调试以及测试。这隔离了错误,使得开发、调试和测试更加容易。编写大型程序时,可以使用自顶向下和自底向上的方法。不要一次性地编写整个程序。使用这些方法似乎浪费了更多的开发时间(因为要反复编译和运行程序),但实际上,它会更节省时间并使调试更容易。
  • 4.更方便团队合作:当一个大问题分解为许多子问题,各个子问题可以分配给不同的编程人员。这更加易于编程人员进行团队工作。


本章小结

1、程序模块化和可重用性是软件工程的中心目标之一。Java提供了很多有助于完成这一目标的有效结构。方法就是一个这样的结构。

2、方法头指定方法的修饰符、返回值类型、方法名和参数。

3、方法可以返回一个值。返回值类型returnValueType是方法要返回的值的数据类型。如果方法不返回值,则返回值类型就是关键字void。

4、参数列表是指方法中参数的类型、次序和数量。方法名和参数列表一起构成方法签名(method signature)。参数是可选的,也就是说,一个方法可以不包含参数。

5、return语句也可以用在void方法中,用来终止方法并返回到方法的调用者。在方法中,有适用于改变正常流程控制是很有用的。

6、传递给方法的实际参数应该与方法签名中的形式参数具有相同的数目、类型和顺序。

7、当程序调用一个方法时,程序控制就转移到被调用的方法。被调用的方法执行到该方法的return语句或到达方法结束的右括号时,将程序控制还给调用者。

8、在Java中,带返回值的方法也可以当作语句调用。在这种情况下,调用函数只要忽略返回值即可。

9、方法可以重载。这就意味着两个方法可以拥有相同的方法名,只要它们的方法参数列表不同即可。

10、在方法中声明的变量称作局部变量。局部变量的作用域是从声明它的地方开始,到包含这个变量的块结束为止。局部变量在使用前必须声明和初始化。

11、方法抽象是把方法的应用和实现分离。用户可以在不知道方法是如何实现的情况下使用方法。方法的实现细节封装在方法内,对调用该方法的用户隐藏。这称为信息隐藏或封装。

12、方法抽象将程序模块化为整齐、层次分明的形式。将程序写成简洁的方法构成的集合会比其他方式更容易编写、调试、维护和修改。这种编写风格也会提高方法的可重用性。

13、当实现一个大型程序时,可以使用自顶向下或自底向上的编码方法。不要一次性编写完整个程序。这种方法似乎浪费了更多的编码时间(因为要反复编译和运行这个程序),但实际上,它会更节省时间并使调试更容易。





第7章 一维数组

7.1 引言

单个的数组变量可以引用一个大的数据集合。

在执行程序的过程中,经常需要存储大量的数据。Java和许多语言都提供了一种称作数组(array)的数据结构,可以用它来存储一个元素个数固定且元素类型相同的有序集。



7.2 数组的基础知识

一旦数组被创建,它的大小是固定的。使用一个数组引用变量,通过下标来访问数组中的元素。

数组是用来存储数据的集合,但是,通常我们会把数组看作一个存储具有相同类型的变量集合会更有用。

无须声明单个变量,只要声明一个数组变量,并且用下标来表示单个变量。

数组中存入的是什么类型的数据,取出来的还是什么类型的数据。


7.2.1 声明数组变量

为了在程序中使用数组,必须声明一个引用数组的变量,并指明数组的元素类型。

声明数组变量的语法:

elementType[] arrayRefVar;(元素类型[] 数组引用变量)

elementType可以是任意数据类型,但是数组中所有的元素都必须具有相同的数据类型。兼容的数据类型是否可以存储

**注意:**也可以用elementType arrayRefVar[](元素类型 数组引用变量[] )声明数组变量。这种来自C/C++语言的风格被Java采纳以适用于C/C++程序员。


7.2.2 创建数组

不同于基本数据类型变量的声明,声明一个数组变量时并不在内存中给数组分配任何空间。它只是创建一个对数组的引用的存储位置。如果变量不包含对数组的引用,那么这个变量的的值为null。除非数组已经被创建,否则不能给它分配任何元素。声明数组变量之后,可以使用下面的语法用new操作符创建数组。

创建数组语法:

arrayRefVar =  new elementType[arraySize]//这条语句做了两件事:1)使用new elementType[arraySize]创建了一个数组;2)把这个新创建的数组的引用赋值给变量arrayRefVar

声明一个数组变量、创建数组、然后将数组引用赋值给变量这三个步骤可以合并在一条语句里。

elementType[] arrayRefVar = new elementType[arraySize]//元素类型[] 数组引用变量  = new 元素类型[数组大小]
elementType arrayRefVar[] = new elementType[arraySize];
//元素类型 数组引用变量  = new 元素类型[数组大小]

使用以下语法给这些元素赋值:

arrayRefVar[index] = value;

注意:一个数组变量看起来似乎是存储了一个数组,但实际上它存储的是指向数组的引用。严格的讲,一个数组变量和一个数组是不同的,但多数情况下它们的差别是可以忽略的。因此,为了简化,通常可以说myList是一个数组,而不用更长的陈述:myList是一个含有10个double型元素数组的引用变量。


7.2.3 数组大小和默认值

当给数组分配空间时,必须指定该数组能够存储的元素个数,从而确定数组大小。

创建数组之后就不能再修改它的大小。可以用arrayRefVar.length得到数组的大小。

当创建数组后,它的元素被赋予默认值,数值型基本元素类型的默认值为0,char型的默认值为’\u0000’,boolean型的默认值为false。String型默认值为null。


7.2.4 访问数组元素

数组元素可以通过下标访问。

数组下标是基于0的,也就是说,其范围从0开始到arrayRefVar.length-1结束。

数组中的每个元素都可以使用下面的语法表示,称为下标变量(indexed variable):

arrayRefVar[index];(数组引用变量[下标])

一些语言使用圆括号引用数组元素,例如myList(9)。而Java语言使用方括号。

创建数组后,下标变量与正常变量的使用方法相同。


7.2.5 数组初始化语法

Java有一个简捷的标记,称作数组初始化语法,它使用下面的语法将声明数组、创建数组和初始化数组结合到一条语句中。

elementType[] arrayRefVar = {value0,value1,....valuek};(元素类型[] 数组引用变量 = {值0,值1,....值k};

警告:数组初始化语法中不使用操作符new。使用数组初始化语法时,必须将声明、创建和初始化数组都放在一条语句中。将它们分开会产生语法错误。


7.2.6 处理数组

处理数组元素时,经常会用到for循环,理由有以下两点:

  1. 数组中所有元素都是同一类型的。可以 使用循环以同样的方式反复处理这些元素。
  2. 由于数组的大小是已知的,所以很自然地就使用for循环。

**提示:**对于char[]类型的数组,可以使用一条打印语句打印。

char[] city = new char[]{'d','a','l','l'};
System.out.println(city);

1)(使用输入值初始化数组)

java.util.Scanner input = new java.util.Scanner(System.in);
System.out.println("Enter " + myList.length + " values: ");
for(int i = 0;i < myList.length;i++)
    	myList[i] = input.nextDouble();

2)(使用随机数初始化数组)下面的循环使用0.0到100.0之间,但小于100.0的随机值初始化数组myList。

for(int i = 0 ;i < myList.length; i++)
    	myList[i] = Math.random() * 100;

3)(显示数组)

for(int i = 0;i < myList.length;i++)
    System.out.print(myList[i] + " ");

4)(对所有元素求和)

double total = 0;
for(int i = 0;i < myList.length; i++)
    total += myList[i];

5)(找出最大元素)

double total = myList[0];
for(int i = 1; i < myList.length; i++)
    if(myList[i]>max)max = myList[i];

6)(找出最大元素的最小下标值)

double max = myList[0];
int indexOfMax = 0;
for(int i = 1; i < myList.length; i++){
    if(myList[i] > max){
        max = myList[i];
        indexOfMax = i;
    }
}

7)(随机打乱)

for(int i = myList.length -1 ; i > 0 ; i--){
    //Generate an index j randomly with 0 <= j <= i
    int j = (int)(Math.random()*(i+1));
    
    //Swap myList[i] with myList[j]
    double temp = myList[i];
    myList[i] = myList[j];
    myList[j] = temp;
}

8)(移动元素)将元素向左移动一个位置并且将第一个元素放在最后一个元素的位置。

double temp = myList[0]; //Retain the first element

//Shift elements left
for(int i = 1;i < myList.length;i++)
    myList[i-1] = myList[i];

//Move the first element to fill in the last position
myList[myList.length -1] = temp;

9)(简化编码)

String[] months = {"January","February",....,"December"};
System.out.println("Enter a month numnber(1 to 12): ");
int monthNumber = input.nextInt();
System.out.println("The month is " + months[monthNumber -1]);

7.2.7 foreach循环

Java支持一个简便的for循环,称为foreach循环,即步使用下标变量就可以顺序地遍历整个数组。

语法格式:

for(elementType element:arrayRefVar){
	//Process the element
}

注意:变量element必须声明为与arrayRefVar中元素相同的数据类型。

但是,当需要以其他顺序遍历数组或改变数组中的元素时,还是必须使用下标变量。

**警告:**越界访问数组是经常会出现的程序设计错误,它会抛出一个运行错误ArrayIndexOutOfBoundsException。为了避免错误的发生,在使用时应确保所使用的下标不超过arrayRefVar.length-1。

程序员经常错误地使用下标1引用数组的第一个元素,但其实第一个元素的下标应该是0.这称为过1错误(off-by-one error)。它是在循环中该使用<的地方误用<=时会犯的错误。



7.3 示例学习:分析数字
/**
 * 程序清单7-1
 *
 * 编写一个程序,找到大于所有项平均值的那些项
 *
 * 现在你可以编写程序来解决本章开始时提出的问题了。问题是,读取100个数字,计算这些数的平均值并找到大于平均值的那些项的个数。为了更加灵活地吹了任意数目地输入,我们让用户给出输入的个数,而不是将其固定为100.
 */
public class AnalyzeNumbers {
    public static void main(String[] args) {
        java.util.Scanner input = new java.util.Scanner(System.in);
        System.out.print("Enter the number of items: ");
        int n = input.nextInt();
        double [] numbers = new double[n];
        double sum = 0;

        System.out.print("Enter the numbers: ");
        for(int i = 0; i < n ; i++){
            numbers[i] = input.nextDouble();
            sum += numbers[i];
        }

        double average = sum / n;

        int count = 0; //The number of elements above average
        for(int i = 0; i < n; i++)
            if(numbers[i] > average)
                count++;

        System.out.println("Average is " + average);
        System.out.println("Number of elements above the average is " + count);
    }
}


7.4 一副牌
/**
 * 程序清单7-2
 *
 * 编写一个程序,从一副牌中随机选出4张牌。
 *
 */
public class DeckOfCards {
    public static void main(String[] args) {

        int[] deck = new int[52];
        String[] suits = {"Spades","Hearts","Diamonds","Clubs"};
        String[] ranks = {"Ace","2","3","4","5","6","7","8","9","10","Jack","Queen","king"};

        //Initialize the cards
        for(int i = 0 ; i<deck.length;i++)
            deck[i] = i;

        //Shuffle the cards
        for(int i = 0; i < deck.length; i++){
            //Generate an index randomly
            int index = (int)(Math.random() * deck.length);
            int temp = deck[i];
            deck[i] = deck[index];
            deck[index] = temp;
        }

        //Display the first four cards
        for(int i = 0;i < 4; i++){
            String suit = suits[deck[i] / 13];
            String rank = ranks[deck[i] % 13];
            System.out.println("Card number " + deck[i] + " : " + rank + " of " + suit);
        }
    }
}


7.5 数组的复制

要将一个数组中的内容复制到另外一个中,你需要将数组的每个元素复制到另外一个数组中。

list2 = list1;

(=)赋值语句并不能将数组1引用的数组内容复制给数组2,而只是将数组1的引用值复制给了数组2.在这条语句之后,数组1和数组2都指向同一个数组。数组2原先所引用的数组不能再引用,它就变成了垃圾,会被Java虚拟机自动收回(这个过程称为垃圾回收)。

在Java中,可以使用赋值语句复制基本数据类型的变量,但不能复制数组。将一个数组变量赋值给另外一个数组变量,实际上是将一个数组的引用复制给另一个变量,使两个变量都指向相同的内存地址。

复制数组有三种方法:

  1. 使用循环语句逐个地复制数组的元素。
  2. 使用System类中的静态方法arraycopy。
  3. 使用clone方法复制数组。

可以使用循环将源数组中的每个元素复制到目标数组中的对应元素。

另一种方式是使用java.lang.System类的arraycopy方法复制数组,而不是使用循环。

arraycopy的语法格式:

arraycopy(sourceArray,srcPos,targetArray,tarPos,length);
//参数srcPos和tarPos分别表示源数组sourceArray和目标数组targetArray中的起始位置。从sourceArray复制到targetArray中的元素个数由参数length指定。

改写:

System.arraycopy(sourceArray,0,targetArray,0,sourceArray.length);

arraycopy方法没有给目标数组分配内存空间。复制前必须创建目标数组以及分配给它的内存空间。复制完成后,sourceArray和targetArray具有相同的内容,但占有独立的内存空间。

**注意:**arraycopy方法违反了Java命名习惯。根据命名习惯,该方法应该命名为arrayCopy(即字母C大写)。



7.6 将数组传递给方法

要点提示:当将一个数组传递给方法时,数组的引用被传给方法。

正如前面给方法传递基本数据类型的值一样,也可以给方法传递数组。

new elementType[]{value0,value2,....valuek};

该数组没有显式地引用变量,这样的数组称为匿名数组(anonymous array)。

Java使用按值传递(pass-by-value)的方式将实参传递给方法。传递基本数据类型变量的值与传递数组值有很大的不同。

  • 对于基本数据类型参数,传递的是实参的值。
  • 对于数据类型参数,参数值是数组的引用,给方法传递的是这个引用。从语义上讲,最好的描述就是参数传递的共享信息(pass-by-sharing),即方法中的数组和传递的数组是一样的。所以,如果改变方法中的数组,将会看到方法外的数组也变化了。

注意:数组在Java中是对象。JVM将对象存储在一个称作堆(heap)的内存区域中,堆用于动态内存分配。



7.7 从方法中返回数组

要点提示:当从方法中返回一个数组时,数组的引用被返回。

可以在调用方法时向方法传递一个数组。方法也可以返回一个数组。



7.8 示例学习:统计每个字母出现的次数
/**
 * 程序清单6-10
 *
 * 创建一个名为RandomCharacter的类,它有五个重载的方法,随机获取某种特性类型的字符。
 *
 */
public class RandomCharacter {
    /** Generate a random character between ch1 and ch2 */
    public static char getRandomCharacter(char ch1,char ch2){
        return (char)(ch1+Math.random()*(ch2-ch1+1));
    }

    /** Generate a random lowercase letter */
    public static char getRandomLowerCaseLetter(){
        return getRandomCharacter('a','z');
    }

    /** Generate a random uppercase letter */
    public static char getRandomUppercaseLetter(){
        return getRandomCharacter('A','Z');
    }

    /** Generate a random digit character */
    public static char getRandomDigitCharacter(){
        return getRandomCharacter('0','9');
    }

    /** Generate a random character */
    public static char getRandomCharacter(){
        return getRandomCharacter('\uu0000','\uFFFF');
    }
}
-----------------------------------------------------------
import testProblem.RandomCharacter;

/**
 * 程序清单7-4
 *
 * 给出一个程序,用于统计一个字符数组中每个字母出现的次数。
 *
 * 1、随机生成1000个小写字母并将其放入一个字符数组中。可以使用请程序清单6-10中RandomCharacter类中的getRandomLowerCaseLetter()方法获取一个随机字母。
 * 2、对数组中每个字母出现的次数进行计数。为了完成这个功能,创建一个具有26个int值的数组counts,每个值存放每个字母出现的次数。
 */
public class CountLettersInArray {
    /** Main mehotd */
    public static void main(String[] args){

        //Declare and create an array
        char[] chars = createArray();

        //Display the array
        System.out.println("The lowercase letters are: ");
        displayArray(chars);

        //Count the occurrences of each letter
        int[] counts = countLetters(chars);

        //Display counts
        System.out.println("\nThe occurrences of each letter are: ");
        displayCounts(counts);

    }

    /** Create an array of characters */
    public static char[] createArray(){
        //Declare an array of characters and create it
        char[] chars = new char[100];

        //Create lowercase letters randomly and assign
        //them to the array
        for(int i = 0;i < chars.length;i++)
            chars[i] = RandomCharacter.getRandomLowerCaseLetter();

        //Return the array
        return chars;
    }


    /** Display the array of characters */
    public static void displayArray(char[] chars){
        //Display the characters in the array 20 on each line
        for (int i = 0; i < chars.length; i++) {
            if((i+1) % 20 ==0)
                System.out.println(chars[i]);
            else
                System.out.print(chars[i] + " ");
        }
    }

    /** Count the occurrences of each letter */
    public static int[] countLetters(char[] chars){
        //Declare and create an array of 26 int
        int[] counts = new int[26];

        //For each lowercase letter in the array,count it
        for(int i = 0; i < chars.length; i++)
            counts[chars[i] - 'a']++;

        return counts;
    }

    /** Display counts */
    public static void displayCounts(int[] counts){
        for(int i = 0 ; i < counts.length;i++){
            if((i+1) % 10 == 0)
                System.out.println(counts[i] + " " + (char)(i+'a'));
            else
                System.out.print(counts[i] + " " + (char)(i + 'a') + " ");
        }
    }
}


7.9可变长参数列表

**要点提示:**具有同样类型的可变长度的参数可以传递给方法,并将作为数组对待。

可以把类型相同但个数可变的参数传递给方法。方法中的参数声明如下:

typeName ... parameterNmae(类型名...参数名)

在方法声明中,指定类型后紧跟着省略号(…)。只能给方法中指定一个可变长参数,同时该参数必须是最后一个参数。任何常规参数必须在它之前。

Java将可变长参数当成数组对待。可以将一个数组或数目可变的参数传递给可变长参数。当用数目可变的参数调用方法时,Java会创建一个数组并把参数传给它。

调用可变长参数方法时,如果没有传参数,数组的长度为0。



7.10 数组的查找

**要点提示:**如果一个数组排好序了,对于寻找数组中的一个元素,二分查找比线性查找更加高效。

查找(searching)是在数组中寻找特定元素的过程。查找是计算机程序设计经常要完成的任务。有很多用于查找的算法和数据结构:线性查找(linear searching)和二分查找(binary searching)。


7.10.1 线性查找法

线性查找法将要查找的关键字key与数组中的元素逐个进行比较。这个过程持续到在列表中找到与关键字匹配的元素,或者查完列表也没有找到关键字为止。如果匹配成功,线性查找法返回与关键字匹配的元素在数组中的下标。如果没有成功,则返回-1.

线性查找法把关键字和数组中的每一个元素进行比较。数组中的元素可以按任意顺序排列。平均来看,如果关键字存在,那么在找到关键字之前,这种算法必须与数组中一半的元素进行比较。由于线性查找法的执行时间随着数组元素个数的增长而线性增长,所以,对于大数组而言,线性查找法的效率并不高。


7.10.2 二分查找法

二分查找法是另一种常见的对数值列表的查找方法。使用二分查找法的前提条件是数组中的元素必须已经排好序。假设数组已按升序排列。二分查找发首先将关键字与数组的中间元素进行比较。

考虑下面三种情况:

  • 如果关键字小于中间元素,只需要在数组的前一半元素中继续查找关键字。
  • 如果关键字和中间元素相等,则匹配成功,查找结束。
  • 如果关键字大于中间元素,只需要在数组的后一半元素中继续查找关键字。

二分法在每次比较之后就排除掉一半的数组元素,有时候是去掉一半的元素,有时候是去掉一半加1个元素。假设数组有n个元素。为了方便起见,假设n是2的幂。经过第1次比较,只剩下n/2个元素需要进一步查找;经过第2次比较,剩下(n/2)/2个元素需要进步一步查找。经过k次比较之后,需要查找的元素就剩下n/2k个。当k=log2n时,数组中只剩下1个元素,就只需要再比较1次。因此,在一个已经排序的数组中用二分查找法查找一个元素,即使是最坏的情况,也只需要log2n+1次比较。对于一个1024(210)个元素的数组,在最坏情况下,二分查找发只需要比较11次,而在最坏的情况下线性查找要比较1023次。

当没有找到这个关键字时,low就是一个插入点,这个位置将插入关键字以保持列表的有序性。一种更实用的方法是返回插入点减1.这个方法必须返回一个负值,表明这个关键字不在该序列中,可以只返回-low吗?答案是:不可以。如果关键字小于list[0],那么low就是0,-0也是0.这就表明关键字匹配list[0]。一个好的选择是,如果关键字不在该序列中,方法返回-low-1.返回-low-1不仅表明关键字不在序列中,而且还给出了关键字应该插入的地方。

**注意:**线性查找法适用于在较小数组或没有排序的数组中查找,但是对大数组而言效率不高。二分查找法的效率较高,但它要求数组已经排好序。



7.11 数组的排序

**要点提示:**如同查找一样,排序是计算机编程中非常普遍的一个任务。对于排序已经开发出很多不同的算法。

选择排序算法:假设要按升序排列一个数列。选择排序法先找到数列中最小的数,然后将它和第一个元素交换。接下来,在剩下的数中找到最小数,将它和第二个元素交换,以此类推,直到数列中仅剩一个数为止。

/**
 * 程序清单7-8
 *
 * 选择排序算法
 */
public class SelectionSort {
    /** The method for sorting the numbers */
    public static void selectionSort(double[] list){
        for (int i = 0; i < list.length-1; i++) {

            //Find the minimum in the list[i...list.length-1]
            double currentMin = list[i];
            int currentMinIndex = i;

            for (int j = i + 1; j < list.length; j++) {
                if(currentMin > list[j]){
                    currentMin = list[j];
                    currentMinIndex = j;
                }
            }

            //Swap list[i] with list[currentMinIndex] if necessary
            if(currentMinIndex != i){
                list[currentMinIndex] = list[i];
                list[i] = currentMin;
            }
        }
    }
}


7.12 Arrays类

java.util.Arrays类包括各种各样的静态方法,用于实现数组的排序和查找、数组的比较 和填充数组元素,以及返回数组的字符串表示。这些方法都有对所有基本类型的重载方法。

sort和paralleSort方法详解:

  • 可以使用sort或者parallelSort方法对整个数组或部分数组进行排序。
  • 可以调用sort(numbers)对整个数组numbers排序。可以调用sort(chars,1,3)对从chars[i]到chars[3-1]的部分数组排序。如果你的计算机有多个处理器,那么parallelSort将更加高效。

可以采用二分查找法(binarySearch方法)在数组中查找关键字。数组必须提前按升序排列好。如果数组中不存在关键字,方法返回-1(插入点下标+1)。

可以采用equals方法检测两个数组是否相a等。如果它们的内容相同,那么这两个数组相等。

可以使用fill方法填充整个数组或部分数组。

还可以使用toString方法来返回一个字符串,该字符串代表了数组中的所有元素。这是一个显示数组中所有元素的快捷和简便的方法。



7.13 命令行参数

要点提示:main方法可以从命令行接收字符串参数。

main方法的声明与众不同,它具有String[] 类型参数args。很明显,参数args是一个字符串数组。main方法就像一个带参数的普通方法。可以通过传递实参来调用一个普通方法。

main方法就和普通方法一样。此外,还可以从命令行传送参数。


7.13.1 向main方法传递字符串

运行程序时,可以从命令行给main方法传递字符串参数。

在命令行中出现字符串时,不需要放在双引号中。这些字符串用空格隔开。如果字符串包含空格,那就必须使用双引号括住。

当调用main方法时,Java解释器会创建一个数组存储命令行参数,然后将该数组的引用传递给args。

然后,Java解释器传递参数args去调用main方法。

注意:如果运行程序时没有传递字符串,那么使用new String[0]创建数组。在这种情况下,该数组是长度为0的空数组。args是对这个空数组的引用。因此,args不是null,但是args.length是0。


7.13.2 示例学习:计算器

Integer.parseInt(args[0])将一个数字字符串转换为一个整数。该字符串必须由数字构成,否则,程序会非正常中断。

使用.符号用于乘法,而不是通常的*符号。原因是当符号*用于命令行时表示当前目录下的所有文件。在使用命令java Test * 之后,下面的程序就会显示当前目录下的所有文件。为了解决这个问题,我们需要使用其他符号来用于乘法操作。

public class Calculator {
    /** Main method */
    public static void main(String[] args) {

        //Check number of strings passed
        if(args.length != 3){
            System.out.println(
                    "Usage: Java Calculator operand1 operator operand2"
            );
            System.exit(0);
        }

        //The result of the operation
        int result = 0;

        //Determine the operation
        switch (args[1].charAt(0)){
            case '+' : result = Integer.parseInt(args[0]) +
                    Integer.parseInt(args[2]);break;
            case '-' : result = Integer.parseInt(args[0]) -
                    Integer.parseInt(args[2]);break;
            case '.' : result = Integer.parseInt(args[0]) *
                    Integer.parseInt(args[2]);break;
            case '/' : result = Integer.parseInt(args[0]) /
                    Integer.parseInt(args[2]);break;
        }

        //Display result
        System.out.println(args[0] + ' ' + args[1] + ' ' + args[2] + " = " + result);
    }
}


本章小结

1、使用语法elementType[] arrayRefVar(元素类型[] 数组引用变量)或elementType arrayRefVar(元素类型 数组引用变量[])声明一个数组类型的变量。尽管elementType arrRefVar[] 也是合法的,但还是推荐使用elementType[] arrayRefVar风格。

2、不同于基本数据类型变量的声明,声明数组变量并不会给数组分配任何空间。数组变量不是基本数据类型变量。数据变量包含的是对数组的引用。

3、只有创建数组后才能给数组元素赋值。可以使用new操作符创建数组,语法如下:new elementType[arraySize](数据类型[数组大小]).

4、数组中的每个元素都是使用语法arrayRefVar[index](数组引用变量[下标])表示的。下标必须是一个整数或一个整数表达式。

5、创建数组之后,它的大小就不能改变,可以使用arrayRefVar.length得到数组的大小。由于数组的下标总是从0开始,所以,最后一个下标总是arrayRefVar.length - 1。如果试图引用数组界外的元素,就会发生越界错误。

6、程序员经常会错误地用下标1访问数组的第一个元素,但是,实际上这个元素的下标应该是0.这个错误称为下标过1错误(index off-by-one error)。

7、当创建一个数组时,如果其中的元素的基本数据类型时数值型,那么赋默认值0。字符类型的默认值为’\u0000’,布尔类型的默认值为false。

8、Java有一个称为数组初始化语法(array initializer)的简捷表达式,它将数组的声明、创建和初始化合并为一条语句,其语法为:

元素类型[] 数组引用变量 = {value0,value1,...,valuek};

9、将数组参数传递给方法时,实际上传递的是数组的引用;更准确地说,被调用地方法可以修改调用者的原始数组的元素。

10、如果数组是排好序的,对于查找数组中的一个元素而言,二分查找比线性查找更加高效。

11、选择排序找到列表中最小的数字,并将其和第一个数字交换。然后在剩下的数字中找到最小的,和剩下列表的第一个元素交换,继续这个步骤,直到列表中只剩下一个数字。





第8章 多维数组

8.1 引言

要点提示:表格或矩阵中的数据可以表示为二维数组。

一维数组可以存储线性的元素集合。可以使用二维数组存储矩阵或表格。



8.2 二维数组的基础知识

要点提示:二维数组中的元素通过行和列的下标来访问。


8.2.1声明二维数组变量并创建二维数组

声明二维数组的语法:

数据类型[][] 数组名;
数据类型 数组名[][];//允许这种方式,但并不推荐使用它。

二维数组中使用两个下标,一个表示行,另一个表示列。同一维数组一样,每个下标索引值都是int型的,从0开始。

警告:使用matrix[2,1]访问行下标为2、列标下为1的特定元素是一种常见错误。在Java中,每个下标必须放在一对方括号中。

也可以使用数组初始化来声明、创建和初始化一个二维数组。


8.2.2 获取二维数组的长度

二维数组实际上是一个数组,它的每个元素都是一个一维数组。数组x的长度是数组中元素的个数,可以用x.length获取该值。


8.2.3 锯齿数组

二维数组中的每一行本身就是一个数组,因此,各行的长度就可以不同。这样的数组称为锯齿数组(ragged array)。

//创建锯齿数组的案例
int[][] triangleArray = new int[5][];
triangleArray[0] = new int[5];
triangleArray[0] = new int[4];
triangleArray[0] = new int[3];
triangleArray[0] = new int[2];
triangleArray[0] = new int[1];

triangleArray[0][3] = 50;
triangleArray[4][0] = 45;

**注意:**使用语法new int[5][]创建数组时,必须指定第一个下标。语法new int[][]是错误的。



8.3 处理二维数组

**要点提示:**嵌套的for循环常用于处理二维数组。

int[][] matrix = new int[10][10];

处理二维数组的例子:

1)(使用输入值初始化数组)

java.util.Scanner input = new java.util.Scanner(System.in);
System.out.println("Enter " + matrix.length + " rows and " + matrix[0].length + " columns: ");
for(int row = 0; row < matrix.length; row++){
    for(int column = 0;column < matrix[row].length; column++){
        matrix[row][column] = input.nextInt();
    }
}

2)(使用随机值初始化数组)

for(int row = 0;row < martix.length;row++){
    for(int column = 0;column < matrix[row].length;column++){
        matrix[row][column] = (int)(Math.random() * 100);
    }
}

3)(打印数组)

for(int row = 0;row < martix.length;row++){
    for(int column = 0;column < martix[row].length;column++){
        System.out.println(matrix[row][column] + " ");
    }
    System.out.println();
}

4)(求所有元素的和)使用名为total的变量存储和。将total初始化为0。

int total = 0;
for(int row = 0 ;row < matrix.length;row++){
    for(int column = 0; column < matrix[row].length;column++){
        total += matrix[row][column];
    }
}

5)(对数组按列求和)对于每一列,使用名为total的变量存储它的和。

for(int column = 0;column<matrix[0].length;column++){
    int total = 0;
    for(int row = 0;row < matrix.length;row++)
        total += matrix[row][column];
    System.out.println("Sum for column " + column + " is "+ total);
}

6)(哪一行的和最大?)使用变量maxRow和indexOfMaxRow分别跟踪和的最大值以及该行的索引值。计算每一行的和,如果计算出的新行的和最大,就更新maxRow和indexOfMaxRow。

int maxRow = 0;
int indexOfMaxRow = 0;

//Get sum of the first row in maxRow
for(int row = 0;column < matrix[0].length;column++){
    maxRow += matrix[0][column];
}

for(int row =1;row < matrix.length;row++){
    int totalOfThisRow = 0;
    for(int column = 0;column < matrix[row].length;column++)
        totalOfThisRow += matrix[row][column];
    
    if(totalOfThiwRow > maxRow){
        maxRow = totalOfThisRow;
        indexOfMaxRow = row;
    }
}

7)(随意打乱)为了实现这个功能,对每个元素matrix[i][j],随机产生下标i1和j1,然后互换matrix[i][j]和matrix[i1][j1]。

for(int i = 0;i < matrix.length;i++){
    for(int j = 0;j < matrix[i].length;j++){
        int i1 = (int)(Math.random() * matrix.length);
        int j1 = (int)(Math.random() * matrix[i].length);
        
        //Swap matrix[i][j] with matrix[i1][j1]
        int temp = matrix[i][j];
        matrix[i][j] = matrix[i1][j1];
        matrix[i1][j1] = temp;
    }
}


8.4 将二维数组传递给方法

将一个二维数组传递给方法的时候,数组的引用传递给了方法。

可以像传递一维数组一样,给方法传递二维数组。也可以从一个方法返回一个数组。

import java.util.Scanner;

/**
 * 程序清单8-1
 *
 * 可以像传递一维数组一样,给方法传递二维数组。也可以从一个方法返回一个数组。
 * 第一个方法,getArray(),返回一个二维数组。
 * 第二个方法,sum(int[][] m),返回一个矩阵中所有元素的和
 */
public class PassTwoDimensionalArray {
    public static void main(String[] args) {
        int[][] m = getArray(); //Get an array

        //Display sum of elements
        System.out.println("\nSum of all elements is "+ sum(m));
    }

    public static int[][] getArray(){
        //Create a Scanner
        Scanner input = new Scanner(System.in);

        //Enter array values
        int[][] m = new int[3][4];
        System.out.println("Enter " + m.length + " rows and " + m[0].length + " columns: ");
        for (int i = 0; i < m.length; i++) {
            for (int j = 0; j < m[i].length; j++) {
                m[i][j] = input.nextInt();
            }
        }
        return m;
    }

    public static int sum(int[][] m) {
        int total = 0;
        for (int row = 0; row < m.length; row++) {
            for (int column = 0; column < m[row].length; column++) {
                total += m[row][column];
            }
        }
        return total;
    }
}


8.5 示例学习:多选题测验评分
/**
 * 程序清单8-2
 *
 * 编写一个可以进行多选题测验评分的程序。
 *
 * 编写一个程序,对多选题测验进行评分。假设这里有8个学生和10道题目,学生的答案存储在一个二维数组中。每一行记录一名学生对所有题目的答案。
 * 正确答案存储在一个一维数组中,
 * 程序给测验评分并显示结果。它将每个学生的答案与正确答案进行比较,统计正确答案的个数,并将其显示出来。
 */
public class GradeExam {
    /** Main Method */
    public static void main(String[] args) {
        //Students' answers to the questions
        char[][] answers = {
                {'A','B','A','C','C','D','E','E','A','D'},
                {'D','B','A','B','C','A','E','E','A','D'},
                {'E','D','D','A','C','B','E','E','A','D'},
                {'C','B','A','E','D','C','E','E','A','D'},
                {'A','B','D','C','C','D','E','E','A','D'},
                {'B','B','E','C','C','D','E','E','A','D'},
                {'B','B','A','C','C','D','E','E','A','D'},
                {'E','B','E','C','C','D','E','E','A','D'},
        };

        //Key to the questions
        char[] keys = {'D','B','D','C','C','D','A','E','A','D'};

        //Grade all answers
        for (int i = 0; i < answers.length; i++) {
            //Grade one student
            int correctCount = 0;
            for (int j = 0; j < answers[i].length; j++) {
                if(answers[i][j] == keys[j])
                    correctCount++;
            }

            System.out.println("Student " + i + "'s correct count is " + correctCount);
        }
    }
}


8.6 示例学习:找出距离最近的点对
import java.util.Scanner;

/**
 * 程序清单8-3
 * 

* 假设有一个集合的点,找出最接近的点对问题就是找到两个点,它们到彼此的距离最近。 * 解决这个问题的方法有好几种: * 一种直观的方法就是计算所有点对之间的距离,并且找出最短的距离。 */ public class FindNearestPoints { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("Enter the number of points: "); int numberofPoints = input.nextInt(); //Create an array to store points double[][] points = new double[numberofPoints][2]; System.out.println("Enter " + numberofPoints + " points: "); for (int i = 0; i < points.length; i++) { points[i][0] = input.nextDouble(); points[i][1] = input.nextDouble(); } // p1 and p2 are the indices in the points' array int p1 = 0, p2 = 1; // Initial two points double shortestDistance = distance(points[p1][0], points[p1][1], points[p2][0], points[p2][1]);// Initialize shortestDistane //Compute distance for every two points for (int i = 0; i < points.length; i++) { for (int j = 0; j < points.length; j++) { double distance = distance(points[i][0], points[i][1], points[j][0], points[j][1]); // Find distance if (shortestDistance > distance) { p1 = i;// Updatep1 p2 = j;// Updatep2 shortestDistance = distance; // Update shortestDistance } } } //Display result System.out.println("The closest two points are " + "(" + points[p1][0] + "," + points[p1][1] + ") and (" + points[p2][0] + "," + points[p2][1] + ")"); } /** * Compute the distance between two points (x1,y1) and (x2,y2) */ public static double distance(double x1, double y1, double x2, double y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } } /* 注意:也可能会有不止一对具有相同最小距离的点对。程序找到这样的一对点。可以在编程练习题8.8中修改这个程序,找出所有距离最短的点对。 提示:从键盘输入所有的点是很繁琐的。可以使用重定向。 */



8.7 示例学习:数独
import java.util.Scanner;

/**
 * 程序清单8-4
 *
 * 要点提示:要解决的问题是检查一个给定的数独解答是否正确。
 * 数独是一个9x9的网格,它被分为更小的3x3的盒子(也称为区域或者块)。将从1到9的数字植入一些称为固定方格(fixed cell)的格子里。该程序的目标是将从1到9的数字植入那些称为自由方格(free cell)的格子,以便能够使得每行每列以及每个3x3的盒子都包含从1到9的数字。
 *
 * 为了方便起见,使用值0表示自由方格,很自然就会使用二维数组表示网格。
 *
 * 为了找到该难题的的解决方案,必须用1到9之间合适的数字替换网格中的每个0.
 * 一旦找到一个数独难题的解决方案,如何验证它是正确的呢?有两种方法:
 *      1、检查是否每行都有1到9的数字以及每列都有1到9的数字,并且每个小的块都有1到9的数字。
 *      2、检查每个单元格。每个单元格必须是1到9的数字,单元格数字在每行、每列,以及每个小方盒中都是唯一的。
 *
 * 提示用户输入一个解决方案,然后报告它是否有效。
 */
public class CheckSudoKuSolution {
    public static void main(String[] args) {
        // Read a Sudoku solution
        int[][] grid = readASolution();

        System.out.println(isValid(grid) ? "Valid solution" : "Invalid solution");
    }

    /** Read a Sudoku solution form the console */
    public static int[][] readASolution(){
        //Create a Scanner
        Scanner input = new Scanner(System.in);

        System.out.println("Enter a Sudokku puzzle solution: ");
        int[][] grid = new int[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                grid[i][j] = input.nextInt();
            }
        }

        return  grid;
    }

    /** Check whether a solution is valid */
    public static boolean isValid(int[][] grid){
        for (int i = 0; i < 9; i++)
            for (int j = 0; j < 9; j++)
                if(grid[i][j] < 1 || grid[i][j] > 9 || !isValid(i,j,grid))
                    return false;

            return true; //The solution
    }

    /** Check wther grid[i][j] is valid in the grid */
    public static boolean isValid(int i,int j,int[][] grid){
        //Check whether gird[i][j] is unique in i's row
        for (int column = 0; column < 9; column++)
            if(column != j && grid[i][column] == grid[i][j])
                return false;

        // Check whether grid[i][j] is unique in j's column
        for (int row = 0; row < 9; row++)
            if(row != i && grid[row][j] == grid[i][j])
                return false;

        //Check whether grid[i][j] is unique in the 3-by-3 box
        for(int row = (i / 3) * 3;row < (i / 3) * 3 + 3;row++)
            for(int col = (j / 3) * 3;col < (j / 3) * 3 + 3;col++)
                if(row != i && col != j && grid[row][col] == grid[i][j])
                    return false;

        return true;//The current value at grid[i][j] is valid
    }

    /**
     * 程序调用readASolution()方法来读取一个数独的解决方案,并且返回一个表示数独网格的二维数组。
     * isValid(grid)方法通过检查每个值是否都是从1到9的数字以及每个网格中值是否都是有效的,来确认网格中是否放入了正确的值。
     * isValid(i,j,grid)方法检查grid[i][j]的值是否是有效的。它检查grid[i][j]在第i行、第j行,以及3x3的方盒中是否出现超过一次。
     * 如何定位同一个方盒中的所有单元格呢?对于任意的grid[i][j],包含它的3x3的方盒的起始单元格是grid[(i/3)*3][(j/3)*3]
     */
}


8.8 多维数组

**要点提示:**二维数组由一个一维数组的数组组成,而一个三维数组可以认为是由一个二维数组的数组所组成。

在Java中,可以创建n维数组,其中n是任意整数。

可以对二维数组变量的声明以及二维数组的创建方法进行推广,用于声明n>=3的n维数组变量和创建n维数组。

声明一个三维数组变量scores,创建一个数组并将它的引用赋值给scres:

double[][][] scores = new double[6][5][2];

多维数组实际上是一个数组,它的每个元素都是另一个数组。三维数组是由二维数组构成的数组,每个二维数组又是一维数组的数组。


8.8.1 示例学习:每日温度和湿度
import java.util.Scanner;

/**
 * 程序清单8-5
 *
 * 假设气象站每天每小时都会记录温度和湿度,并且将过去十天的数据都存储在一个名为Weather.txt的文本文件中。文件中每一行包含四个数字,分别表示日期、小时、温度和湿度。
 * 注意,文件的行不一定是要按照日期和小时的升序排列。
 *
 * 编写任程序,计算这十天的日均温度和日均湿度。可以使用输入重定向来读取文件,并将这些数据存在一个名为data的三维数组中。data的第一个下标范围从0到9,代表10天;第二个下标范围从0到23,代表24小时;而第三个下标范围从0到1,分别代表温度和湿度。
 *
 * 注意:在文件中,天是从1到10编号的,而小时是从1到24编号的。因为数组下标是从0开始的,所以,data[0][0][0]存储的是第一天第一小时的温度,而data[9][23][1]存储的是第十天第二十四小时的温度。
 */
public class Weather {
    public static void main(String[] args) {
        final int NUMBER_OF_DAYS = 10;
        final int NUMBER_OF_HOURS = 24;
        double[][][] data = new double[NUMBER_OF_DAYS][NUMBER_OF_HOURS][2];

        Scanner input = new Scanner(System.in);
        //Read input using input redirection form a file
        for(int k = 0; k < NUMBER_OF_DAYS * NUMBER_OF_HOURS; k++){
            int day = input.nextInt();
            int hour = input.nextInt();
            double temperature = input.nextDouble();
            double humidity = input.nextDouble();
            data[day -1][hour -1][0] = temperature;
            data[day -1][hour -1][1] = humidity;
        }

        //Find the average daily temperature and humidity
        for(int i = 0;i < NUMBER_OF_DAYS;i++){
            double dailyTemperatureTotal = 0,dailyHumidityTotal = 0;
            for(int j = 0;j < NUMBER_OF_HOURS;j++){
                dailyTemperatureTotal += data[i][j][0];
                dailyHumidityTotal += data[i][j][1];
            }

            //Display result
            System.out.println("Day " + i + "'s average temperature is " + dailyTemperatureTotal / NUMBER_OF_HOURS);
            System.out.println("Day " + i + "'s average humidity is " + dailyHumidityTotal / NUMBER_OF_HOURS);
        }
    }
}

8.8.2 示例学习:猜生日
import java.util.Scanner;

/**
 * 程序清单8-6
 * 

* 给出一个猜生日的程序,可以通过用三维数组来存储5个数字集来简化程序,然后使用循环提示用户回答。 */ public class GuessBirthdayUsingArray { public static void main(String[] args) { int day = 0; // Day to be determined int answer; int[][][] dates = {{ {1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}, {25, 27, 29, 31}}, { {2, 3, 6, 7}, {10, 11, 14, 15}, {18, 19, 22, 23}, {26, 27, 30, 31} }, { {4, 5, 6, 7}, {12, 13, 14, 15}, {20, 25, 26, 27}, {28, 29, 30, 31} }, { {8, 9, 10, 11}, {12, 13, 14, 15}, {24, 25, 26, 27}, {28, 29, 30, 31} }, { {16, 17, 18, 19}, {20, 21, 22, 23}, {24, 25, 26, 27}, {28, 29, 30, 31}}}; //Create a Scanner Scanner input = new Scanner(System.in); for(int i = 0; i < 5; i++){ System.out.println("Is your birthday in Set" + (i + 1) + "?"); for(int j = 0; j < 4; j++){ for(int k = 0;k < 4; k++) System.out.printf("%4d",dates[i][j][k]); System.out.println(); } System.out.println("\nEnter 0 for No and 1 for Yes: "); answer = input.nextInt(); if(answer == 1) day += dates[i][0][0]; } System.out.println("Your birthday is " + day); } }



本章小结

1、可以使用二维数组来存储表格。

2、可以使用以下语法来声明二维数组变量:

元素类型[][] 数组变量

3、可以使用以下语法来创建二维数组变量:

new 元素类型[行的个数][列的个数]

4、使用下面的语法表示二维数组中的每个元素:

数组变量[行的个数][列的个数]

5、可以使用数组初始化语法来创建和初始化二维数组:

元素类型[][] 数组变量 = {{某行的值},....,{某行的值}}

6、可以使用数组的数组构成多维数组,例如:一个三维数组变量可以声明为:

元素类型[][][] 数组变量,
使用new 元素类型[size1][size2][size3]来创建三维数组。




你可能感兴趣的:(Java语言程序设计读书笔记,java)