Java-数学学习手册-全-
Java 数学学习手册(全)
一、介绍
市场上有很多好的 Java 编程书籍,但是对于一个刚接触 Java 并且只有很少编程知识的初学者来说,找到一本合适的并不容易。
这本书将帮助初学者学习如何有效地用 Java 编程。我的意图是简化 Java 更复杂的方面,并指导学习者探索“幕后”的东西我希望这本书里面的说明足够直观,读者可以通过实践来理解。
有编程经验的人都明白,数学知识在程序设计中起着至关重要的作用。所以拥有良好的数学功底在学习编程的时候无疑是超级有帮助的。这本书提供了一个在编程环境中练习数学问题解决的好机会。带着这个动机,我加入了一些适用于编程相关概念的数学练习题。
通过有意识的练习来学习可以在更深的层次上增强你对新概念的理解。积极参与动手项目是学习过程中的一个重要部分。更多的练习会更快产生效果。我希望这对你来说是一次愉快的学习经历。
编程工作包括使用某种计算机语言设计和编写代码。正确执行的代码将执行重复的任务并完成预期的目标。如今,随着高科技产品融入我们的日常生活,计算机编程技能几乎在任何地方都变得不可或缺。许多日常计算工作已经被编程设备所取代——除了当地超市的自助结账队伍或不断增加的网上购物数量,你不需要看得太远。重复发生的事件越来越多地由自动化系统控制,如建筑安全系统、安装在你房子里墙上的恒温器,以及许多其他例子。
另一个例子是游戏软件,它有如此丰富的用户界面,以至于我们许多人——从青少年到成年人——都已经沉迷其中。所有这些产品和服务本质上都是由计算机编程构建的。
随着人工智能之美的出现,我们已经比以往任何时候都更能看到和感受到计算机技术应用的力量。如果你看过好莱坞电影,比如《抵达》或【乘客】(均于 2016 年上映),我相信你会被电影中描绘的智能机器人迷住。如果你对计算机如何精确识别任何图片中的活动对象感到好奇,我建议你听一听一个激动人心的 TED 演讲,题为“我们如何教计算机理解图片”。所有这些令人惊讶的事情都是由软件实现的,软件是用编程语言编写的。
要成为一名优秀的程序员,你需要了解逻辑控制和基本的计数方法。如果你想开发一个系统来控制对象的活动,这将需要更复杂的数学知识。
数学史上有相当多著名但尚未解决的问题。随着计算机技术的进步,我们可以利用计算机的才能来解决这些问题。
例如,Collatz 猜想指出,如果你随机挑选一个正整数 N,如果它是偶数,除以 2;如果是奇数,就乘以 3 再加 1。如果你重复这个过程足够长的时间,最终 N 的最终结果总是 1。
数学家和数据研究人员尝试了数百万个数字。没有发现例外,但是没有人找到证明所有整数都遵循这个规律的方法。
利用简单的 Java 编程,我们可以证明任意正整数到 n 的 Collatz 猜想,在下面的短程序中,我将用每一个整数来检验这个猜想,找出它的序列长度,也就是它达到结果“1”的运算次数。
public class ProveIt {
public static void main(String[] args) {
// representation of a million
final long N = 1000 * 1000;
for(long i = 1; i <= N; i++) {
System.out.println("i=" + i + " - " +
GetCollatzSequenceCount(i));
}
System.out.println("DONE!");
}
private static long GetCollatzSequenceCount(long n) {
if (n <= 0) return 0;
long count = 0;
while(true) {
if (n == 1) return count;
if (n % 2 == 0) {
n /= 2;
} else {
n = n * 3 + 1;
}
count++;
}
}
}
你猜怎么着?为了测试多达 1,000,000 个整数,它在普通工作笔记本电脑上几秒钟内完成执行并报告结果。现在不要担心理解或运行这段代码;要知道,这个短程序可以在几秒钟内完成 100 万次迭代。
输出的最后一部分是:
i=999991 - 165
i=999992 - 113
i=999993 - 165
i=999994 - 113
i=999995 - 258
i=999996 - 113
i=999997 - 113
i=999998 - 258
i=999999 - 258
i=1000000 - 152
DONE!
关于本书中的符号还有最后一件事要提:
-
数学 :描述一个具体的数学概念。
-
问题 :提供后续练习列表。可以找到一些问题的提示。
-
提示 :为参考解决问题建议思路。
-
最后,鼓励学生尝试 实验室工作后学习 答案例题* 。*
*## 问题
-
列举一个你观察到的同时满足以下(a)和(b)的例子。
-
现在没有与之相关的编程功能。
-
如果有一个内置程序,它的运行效率会高得多。
-
-
我们如何在两个杯子之间交换不同类型的水?
不允许你掺水。
- 我在考虑 1 到 100 之间的一个整数。你可以问我一些问题来确定这个整数,但是你不能问类似“这个整数是什么?”
为了算出数字,你问最少数量问题的策略是什么?
-
有 27 个乒乓球。它们看起来都一样,重量也一样,除了其中一个更轻。使用天平,您如何快速找到与其他产品不同的产品?
-
如何使用以下四个数字和基本运算符(“+”、“-”、“x”和“/”)来创建一个等于 24 的数学公式?每个数字只能使用一次,但可以使用括号。
*
二、数字基础
什么是数制?
存在许多不同的数字系统,因为有特定的用途,其中某个数字系统使用起来更方便,并且比其他数字系统更有优势。例如:
-
重量:1 磅= 16 盎司
-
长度:1 码= 3 英尺,1 英尺= 12 英寸
-
巴比伦数字:基数 60
(来自维基百科)
- 在中国古代:阴阳——二进制,八卦——八卦图
(来自维基百科)
-
十进制计数
- 十个符号:0–9
-
二进制计数
- 两个符号:0 和 1
-
时间测量
一天= 24 小时
一小时= 60 分钟= 3600 秒
为什么人用十进制数,而计算机用二进制数?
一个简单的答案是,人类有十个手指和十个脚趾,而计算机只有两种状态。
开玩笑地说,计算机是由许多连接和组件(部件)组成的,用于传输和存储数据,以及与其他组件通信。大多数存储、传输和通信事件都发生在数字电子设备中。数字电子设备使用二进制系统(开或关)。具有一系列开/关脉冲的信号等于一个二进制数。
如何在不同的数字系统之间转换数字
【数学】十进制和二进制之间的转换:
-
将十进制数转换成二进制数
[示例]
将基数为 10 的 350 转换为二进制数(基数为 2)
[回答]
以 10 为基数,我们可以用这个等式写出 350:
350 = 3 * 102+5 * 101+0 * 100
请注意,每个系数(即 3、5 和 0)都小于 10,并且没有 10 3 或以上的系数。
现在我们想把它改成这样:
350 = a * 28+b * 27+c * 26+d * 25+e * 24+f * 23+g * 22+h * 21+I * 20
注意没有 2 9 或以上,因为我们知道 350 < 512=2 9
350–1 * 256(即 28)= 94<128 = 27a = 1,b = 0;
94–1 * 64(即 26)= 30<32 = 25c = 1,d = 0;
30–1 * 16(即 24)= 14e = 1;
14–1 * 8(即 23)= 6f = 1;
6–1 * 4(即 22)= 2g = 1;
2–1 * 2(即 2 1 ) = 0 h = 1,I = 0;
所以 350 = 1 * 28+0 * 27+1 * 26+0 * 25+1 * 24+1 * 23+1 * 22+1 * 21+0 * 20
也就是说(350)10=(101011110)2
下标数字(10 和 2)表示它的基数。
-
将二进制数转换成十进制数
[示例]
将二进制数 11001001 转换为十进制数
【回答】
我们重写了二进制数的表达式,如下所示。
(11001001) 2
= 1 * 27+1 * 26+0 * 25+0 * 24+1 * 23+0 * 22+0 * 21+1 * 20
= 128 + 64 + 8 + 1
= (201) 10
要练习十进制和二进制的转换,我推荐这个网游:
games . pen JEE . com/binary-numbers-game/
【数学】十进制和二进制的分数
-
将十进制数(基数为 10)转换为二进制数
我们需要了解如何识别小数点后的每一位数字。例如,4.3256
去掉整数部分“4”,我们得到 0.3256。
0.3256 x 10 = 3.256 3 是小数点后的第一位数字
删除整数部分“3”,所以我们现在有 0.256
0.256 x 10 = 2.56 2 是小数点后的第二位数字
去掉整数部分“2”,所以我们现在有 0.56
0.56 x 10 = 5.6 5 是小数点后的第三位数字
去掉整数部分“5”,所以我们现在有 0.6
0.6 x 10 = 6 6 是小数点后的第四位数字
去掉整数部分“6”,我们就完成了。
当我们把一个分数从十进制转换成二进制时,同样的过程也适用。
“4.3256”的整数部分是“4”,在二进制中是 100。
从现在开始,我们只看小数部分。
0.3256 x 2 = 0.6512 0 是小数点后的第一位数字
0.6512 x 2 = 1.3024 1 是第二位数字
0.3024 x 2 = 0.6048 0 是第三位数字
0.6048 x 2 = 1.2096 1 是第 4 位数字
0.2096 x 2 = 0.4192 0 是第 5 位数字
0.4192 x 2 = 0.8392 0 是第 6 位数字
0.8392 x 2 = 1.6784 1 是第 7 位数字
……
重复,直到我们最终得到 0,或者我们看到一个重复的模式。
(100.0101001…) 2 为最终答案。
【数学】二进制算术:加、减、乘、除、平方根
二进制加法和减法运算遵循如下规则:
0 + 0 = 0 0 - 0 = 0
0 + 1 = 1 1 - 0 = 1
1 + 0 = 1
1 + 1 = 0(进位 1)= 1010-1 = 1
注意
与我们熟悉的十进制数字系统(也称为以 10 为基数的数字)相反,二进制数字以 2 为基数,每一位只有 0 或 1 来表示。在加法运算中,当任何一个数字达到 2 时,它的左边数字就变成“进位 1”。然而,在减法运算中,一个数字 0 需要从它的左边数字借 2 来减去 1。然而,这是与“进位”相反的操作方向
二进制乘法和除法运算遵循如下规则:
-
0 x 0 = 0
-
0 x 1 = 0
-
1 x 0 = 0
-
1 x 1 = 1
这是二进制数除法的一个例子。
-
11__
-
- 1011
-
−11_
-
101
-
−11
-
10 余数(r)
二进制和其他数字系统之间的转换:
- 十六进制-以 16 为基数的数字系统
十进制和十六进制之间的映射:
|十六进制:
|
Zero
|
one
|
Two
|
three
|
four
|
five
|
six
|
seven
|
eight
|
nine
|
A
|
B
|
C
|
D
|
E
|
F
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 十进制: | Zero | one | Two | three | four | five | six | seven | eight | nine | Ten | Eleven | Twelve | Thirteen | Fourteen | Fifteen |
由于二进制中的每四个数字构成一个十六进制数字,为了将二进制数转换为十六进制数,我们从右开始对二进制中的每四个数字进行分组。
比如二进制的 100 等于十六进制的 4,二进制的 1011 等于十六进制的 8 + 2 + 1 = B。所以二进制的 1001011,十六进制就是 4B。
- 八进制-以 8 为基数的数字系统
二进制中的每三个数字组成一个八进制数字。我们可以将二进制中的每三个数字从最右边开始分组,并将其转换为八进制形式。
例如,
要将二进制的 10111011 转换为八进制的结果:
第一步——从右起每三位数分组:(10)2(111)2(011)2;
第二步——将每组最多三位数(0 到 1)转换为一个八进制数(0 到 7):(2)8(7)8(3)8;
步骤 3 -转换后的八进制结果是 273 8 。
相反,要将八进制的 273 转换为二进制格式,我们将每个八进制数字转换为三位二进制数字:
- 2738=(2)8(7)8(3)8=(010)2(111)2(011)2= 0101110112
什么是比特、字节、KB、MB、GB、TB、PB?
位表示二进制数字,0 或 1。它是数据的最小单位。
字节是八位的序列。
1 字节(= 8 位)、KB、MB、GB、TB、PB
| 1024 字节= 1 KB | KB:千字节 | | 1024 kb = 1mb | 兆字节 | | 1,024 MB = 1 GB | GB:千兆字节 | | 1.024 GB = 1 TB | TB:TB:TB | | 1,024 TB = 1 PB | PB:PB |什么是按位?
在计算机中,一个整数在内存中被表示为一个比特序列。我们通常通过计算机的图形用户界面与显示器上的十进制数字进行交互。然而,它的二进制形式在计算机内部执行实际的计算。按位只是涉及处理单个位的一级运算。
按位运算符包含三种基本运算符:
& 还有
-
0 & 0 = 0 & 1 = 1 & 0 = 0
-
1 & 1 = 1
-
如果第一位为 1,第二位为 1,则每个位对的逻辑与(&)结果为 1。否则,结果为零。
-
示例:
-
01 & 00 = 00
-
11111111 & 01100101 = 01100101
| 或者
-
否则,结果为零。
-
示例:
-
0101 | 0011 = 0111
-
0010 | 1000 = 1010
-
如果第一位是 1 或者第二位是 1。
-
或者,如果第一位和第二位都是 1。
-
0 | 0 = 0
-
0 | 1 = 1 | 0 = 1 | 1 = 1
-
每个位对的逻辑或(|)导致 1,
^ 没有
-
一元运算对每个位执行逻辑否定。
-
换句话说,在这个操作之后,1 位被翻转为 0 位,0 位被翻转为 1 位。
-
示例:
-
^ 0011 = 1100
-
^ 01010110 = 10101001
问题
-
为什么计算机使用二进制数?
-
十六进制、八进制和按位是什么意思?
三、Java 基础
今天,计算机使用如此多不同种类的编程语言。它们中的每一个都在解决特定类型的问题的某个领域中起着重要的作用。Java 是 Sun Microsystems 在 90 年代初发明开发的;经过 20 年的发展,Java 现在已经成为世界上最流行的编程语言之一。
Java 是一种典型的面向对象编程语言,也称为面向对象编程语言,它处理一堆“对象”这些对象包含数据和操作。操作是关于可以对对象中的数据做什么。
在 Java 世界中,有一种开源开发工具叫做 Eclipse。它有丰富的功能,并且可以免费使用。Eclipse 也是一个适合初学者的工具。我们将使用 Eclipse 开始探索 Java 世界。
Java 有什么特点?
面向对象
对象是具有某些属性的“东西”(也称为特性)。该对象执行一组操作(也称为方法)。这些操作定义了对象的行为。
基于类
一个类仅仅是一种对象的代表。它是描述对象细节的模板。一个类由三个元素组成:名称、属性和操作。
Java 位元组码
Java 字节码是 Java 虚拟机(JVM)的机器语言。当 Java 字节码在机器上运行时,它将被 JVM 翻译成特定于机器的本机代码。因此,在 Windows 计算机上,JVM 字节码被翻译成特定于 Windows 的本机代码;在 Linux 计算机上,它被翻译成特定于 Linux 的本机代码。这被称为一次写入,随处运行(如图 3-1 所示)。
图 3-1
一次编写,随处运行
当 JVM 加载一个类文件时,它为类中的每个方法获得一个字节码流。当一个方法在程序执行期间被调用时,该方法的字节码被执行。
尽管这对初学者来说可能有些难以理解,但我们还是想介绍一些内置于 Java 语言及其运行时引擎中的其他强大特性。
-
多线程
Java 语言支持多线程功能,这使得多个任务可以同时运行。这个特性增强了 Java 代码的计算能力,并使 Java 应用程序具有很高的响应能力。
-
安全代码
Java 不像其他语言(如 C 或 C++)那样使用指针。这避免了传统的安全漏洞。除了运行时检查,Java 还在编译期间使用严格的规则进行静态类型检查。Java 有它的异常处理来捕捉意外的错误。当用户通过网络等获取代码时,Java 提供了一种加密安全机制。这些安全功能使 Java 成为比其他语言更安全的编程语言。
-
碎片帐集
Java 有自己独特设计的内存管理机制。与 C/C++语言不同,Java 不要求开发人员根据何时注册或释放内存来管理内存。它会自动收集并释放未使用的内存。这使得开发工作变得更加容易。
最后要提到的但并非最不重要的强大特性是 Java 极其丰富的开源库。这是 Java 越来越受开发人员欢迎的一个重要原因。而且,因为 Java 的开发者社区正在变得越来越强大,随着时间的推移,Java 将会发展成为一种更加强大的编程语言。
四、开始玩 Java 吧
下载并安装一个 Java 运行时环境(根据你电脑的操作系统选择合适的版本): https://www.java.com/en/download/manual.jsp
下载安装 Eclipse(寻找最近的稳定版本): http://www.eclipse.org/downloads/
安装后,您应该会在桌面上看到一个图标,如图所示——“Neon”版本。
一旦您启动 Eclipse,您将需要指定工作空间(图 4-1 )。
图 4-1
指定工作空间
JRE 和 JDK 有什么区别?
JRE 是“Java 运行时环境”。它是你的 Java 程序运行的地方。JDK 是“Java 开发工具包”,它是 Java 的全功能软件开发工具包,包括 JRE、编译器和用于创建和编译程序的工具(例如 JavaDoc、Java 调试器)。
当您只想在浏览器或计算机上运行 Java 程序时,您将安装 JRE。但是如果你想做一些 Java 编程,你还需要安装 JDK。
图 4-2 显示了 JRE 和 JDK 之间的清晰关系,以及它们的基本特征区域。
图 4-2
JDK 和 JRE 比较
什么是工作空间、源代码和包?
“工作区”用于将一组相关的项目组合在一起。通常这些项目会组成一个应用程序。
“源代码”是指源代码,即 Java 程序和相关代码。
“包”表示文件的集合。
什么是编辑、编译和执行?
“编辑”用 Java 语言编写代码。
“编译”将 Java 源代码转换成 Java 字节码。
“执行”运行程序。
-
编辑创建
*.java
文件 -
编译生成
*.class
文件
创建您的第一个程序
让我们开始吧:
-
Once Eclipse is launched, left-click on “File” in the top menu bar and left-click on “New” in the drop-down menu. Then select “Java Project” from another drop-down menu as shown in Figure 4-3.
图 4-3
在我们创建 Java 项目或 Java 类之前
-
Now create a Java project named
MyFirstProgram
, as shown in Figure 4-4. Click “Finish.”图 4-4
创建 Java 项目
-
Select File ➤ New ➤ Class to create a Java class (name: “
Welcome
”), as shown in Figure 4-5. Make sure you select “public static void main(String[] args).” Click on “Finish.”图 4-5
创建 Java 类
-
自动创建
Welcome
类和public static void main(String[] args)
方法,如图 4-6 所示。然后手动添加以下输出行: -
Click on “Run” from the top menu bar, and then click on “Run” from its drop-down menu, we will see output text in the Console window as shown in Figure 4-6.
图 4-6
运行应用程序
System.out.println("Hello, friend, you are welcome!");
探索类和 main()
正如你在第三章中看到的,“类”是一个模板,它描述了一个对象应该显示的行为。您可以从类中创建单个对象。这被称为“类实例化”一个类有局部变量、实例变量、类变量和许多方法。
main()
是一个方法名。当您的 Java 程序被执行时,运行时通过首先调用它的main()
方法来启动您的程序。main()
方法是 Java 程序的一个入口点。
为什么是“public static void main(String[]args)”。
这是由 Java 语言和 JVM 设计的一个约定(如果其中一些没有意义,请不要担心,我们将在本书的后面回到它)。
-
main
是方法的名称; -
String[] args
是将main()
方法输入的参数作为字符串数组的数据类型;传递给main()
方法的字符串值称为参数;它们可以作为可选值,在程序启动时发送给程序; -
void
表示main()
方法调用没有返回数据; -
public
表示main()
方法可供 JVM 调用,以启动整个程序的执行; -
static
表示不能用对象实例调用main()
方法;换句话说,JVM 可以直接调用它,而不必创建额外的结构来调用它。
如果您将public
更改为private
,您将在运行时看到以下错误。
Error: Main method not found in class <your class name>, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
问题
-
Something.java
档和Something.class
档有什么区别?-
一个
.java
文件是一个大得多的二进制文件,而一个.class
文件是一个较小的压缩版本。 -
.class
文件用于面向对象编程,而.java
文件用于过程编程。 -
一个
.java
文件包含用 Java 语言编写的代码,一个.class
文件包含用 C++语言编写的代码。 -
程序员先写
.class
文件,后面自动生成.java
文件。 -
Something.java
是程序员打出的源代码文件,Something.class
是编译好的可执行类文件,由计算机运行。
-
-
下面哪个方法头是正确的?
-
public static foo void[]
-
public static void foo()
-
public void static foo{}
-
public void static foo()
-
public static foo()
-
五、变量
一个变量是:
-
存储的位置
-
存储某种信息供以后使用的容器
-
可通过引用描述所述信息的名称来检索
有不同类型的变量:
-
局部变量
它们是仅在局部范围内有效的变量,例如在方法内部或代码块内部。
-
类变量和实例变量
我们将在后面的章节中学习基本的类概念时看到例子。类变量定义类字段和属性的数据类型。当从一个类创建一个对象时,该对象的类变量成为实例变量。类变量和实例变量都在类中声明,但是它们不属于该类的任何方法。
-
方法参数
参数也是变量,用于将值从方法外部传递到方法中。
定义变量名
变量名:
-
不能以数字或某些特殊符号开头,如引号(
"
)或类似于)
的括号等。,但可以以下划线(_
)或美元符号($
)开头。 -
不能是该语言中已经使用的任何关键字,也称为保留字,例如,
if
、else
等。
例子
以下哪一项可以在 Java 程序中用作标识符?答案不止一个。
-
ABC
-
B4
-
24isThesolution
-
"hello"
-
AnnualSalary
-
_average
-
for
-
sum_of_data
-
first-name
-
println
答案:1、2、5、6、8 和 10
您可以键入如图 5-1 所示的简单代码来检查哪些字符串不适合变量名,因为在 Eclipse 的 Java 代码编辑器中,所有语法错误都用红色下划线标出。
图 5-1
突出显示错误
Java 中不同类型的变量
变量的类型将定义数据的类型以及它们存储在变量中时的大小。Java 提供了八种基本的数据类型。Java 还支持非原始数据类型的引用或对象数据类型。
原始类型:
-
int
-
long
-
short
-
float
-
double
-
char
-
byte
-
boolean
参考类型:
-
String
-
对象、数组等等
当我们在程序中声明一个变量时,我们实际上是在计算机的内存中为操作预留了空间。有必要了解常见的数据类型及其占用的内存空间。此表显示了数据类型、它们的位(和字节)大小以及它们所代表的值类型的列表。
|类型
|
位数
|
价值
|
| --- | --- | --- |
| (同 Internationalorganizations)国际组织 | 32 位元(= 4 位元组) | 整数 |
| 短的 | 16 位(= 2 字节) | 整数 |
| 长的 | 64 位元(= 8 位元组) | 整数 |
| 字节 | 8 位(= 1 字节) | 整数 |
| 漂浮物 | 32 位元(= 4 位元组) | 浮点 |
| 两倍 | 64 位元(= 8 位元组) | 浮点 |
| 茶 | 16 位(= 2 字节) | unicode 字符 |
| 布尔 | 见下文 | 对/错 |
对于布尔数据类型,只有两个不同的值,true
或false
。一个小小的空间似乎正合适。事实上,Java 实际上为布尔数据类型准备了至少一个字节的空间,尽管它只使用了一位的空间。确切地说,它没有明确定义,因为它将依赖于平台的虚拟机。
给变量赋值
以下是如何将值或内容分配给特定类型的变量:
int number1 = 3;
int number2 = 7;
int total = number1 + number2;
boolean flag = true;
String a = "welcome";
String b = "my friend";
String c = a + b;
然后,您可以使用下面的方法来显示和验证变量的当前值。例如,这一行将显示字符串变量c
的当前值:
System.out.println(c);
实验室工作
参照我们创建的第一个程序,按照描述使用System.out.println()
语句;编译并运行它;然后在控制台窗口中,验证每个操作后每个变量的结果值。
基本数学运算:
int number = 9 / 8;
double number = 9 / 8;
有序的数学运算:
int number = 6 + 8 * 5;
int number = (6 – 8) * (5 + 3);
六、第一个算法
今天有许多不同类型的算法在计算机上运行。算法定义了计算机需要遵循来解决特定问题的一组指令。智能和高性能的算法导致精确和高效的工作结果。
接下来是一个使用真实对象创建算法的示例。在这里,我们将看看如何在两个相同大小的容器之间交换不同种类的水(淡水和海水)。根据常识,我们知道使用第三个同样大小的空容器。以下是完成这项工作需要采取的一系列行动:
-
将容器 A 中的淡水倒入容器 C(空的);
-
将海水从容器 B 倒入容器 A;
-
将 C 容器中的淡水倒入 b 容器,任务完成。
从这张表中你可以看到每个容器中的水在每一步之后是如何变化的。
| |操作
|
容器
|
容器
|
容器
|
| --- | --- | --- | --- | --- |
| 开始 | 介绍 | 淡水 | 海水 | 空的 |
| 在步骤 1 之后 | A C | 淡水->空的 | 海水 | 空->淡水 |
| 在步骤 2 之后 | B A | 空->海水 | 海水->空的 | 淡水 |
| 在步骤 3 之后 | C B | 海水 | 空->淡水 | 淡水->空的 |
在许多程序中,我们经常会遇到需要将一个变量的值设置为另一个变量的值的情况。让我们应用从上一个例子中学到的相同逻辑来交换两个变量之间的值。换句话说,我们将实现我们之前定义的算法。
在变量之间交换值
假设两个整数,a = 5
和b = 4
。我们想改变他们的价值观,使其成为a = 4
和b = 5
。按照下表中列出的操作顺序,变量a
和b
中的值将被切换。
步骤
|
操作
|
a
|
b
|
c
|
| --- | --- | --- | --- | --- |
| 0
| int a = 5; int b = 4;
| five | four | |
| 1
| int c = a;
| five | four | five |
| 2
| a = b;
| four | four | five |
| 3
| b = c;
| four | five | five |
其他方法
您可以使用其他方法在两个整数之间交换值,而不使用临时变量。一种方法是利用+
和–
运算符来交换值:
-
a = a + b;
现在a = 9, b = 4
-
b = a – b;
现在a = 9, b = 5
-
a = a – b;
现在a = 4, b = 5
成功完成!
七、输入和输出
在计算机程序的运行期间,程序可以要求用户输入数据,读取用户的输入,然后向用户显示输出结果。Scanner
是我们用来在控制台窗口上实现用户交互功能的工具。
导入 java.util.Scanner
在一个包中预定义了Scanner
实用程序类及其方法。我们使用import
语句将Scanner
类与我们正在创建的程序集成在一起。
-
方法 1。在 Java 代码的顶部添加第
import java.util.Scanner;
行,然后在main()
类中添加以下内容: -
方法 2。直接在 Java 代码中键入代码
Scanner input = new Scanner(System.in);
,然后使用 Eclipse 的智能感知来选择正确的修复。换句话说,Eclipse 将建议您添加import
语句,因为它发现您可能需要它。因此,
import java.util.Scanner;
将被添加到类的顶部。
Scanner input = new Scanner(System.in);
获取输入
从程序中读取用户输入数据有几种方法:
-
nextLine():读取一个字符串输入
-
next():读取一个字符串输入
-
nextInt():读取整数输入
-
nextFloat():读取浮点数输入
nextLine()
和next()
有什么区别?
-
next()只读取输入,直到空格。
它不能阅读由空格分开的两个单词。在读取输入流后,它将光标放在同一行,这意味着它不会改变行。
-
nextLine()读取输入直到行尾('
\n
')。它会在返回当前行后自动向下移动扫描仪。
生产产量
System.out.println
是在控制台窗口中显示文本的常用方式。开发人员通常使用它来读取用户的输入,向用户提供一般信息,并在运行时记录信息(到控制台),以便找出关键变量的情况。
我们经常使用以下特殊字符(即转义字符)来控制输出格式:
-
+
:连接两个字符串; -
\n
:换行符; -
\t
:在制表符宽度对齐文本的制表符; -
\\
:反斜杠字符; -
\r
:回车符; -
\"
和\"
:双引号字符。
这里有一个例子:
System.out.println("This demonstrates " + "\"how to display a table format\".\n");
System.out.println("123\t45\t6789\nab\tcde\tf");
这将生成以下输出:
This demonstrates "how to display a table format".
123 45 6789
ab cde f
实验室工作
练习使用语句System.out.println()
来:
-
使用
+
显示两个子字符串“我是”和“一名开发人员”之间的字符串连接 -
显示新行
-
使用
\"
和\"
显示报价
例子
以下哪一项是输出消息的正确语法?
-
System.out.println("Hello, world!");
-
System.println.out('Hello, world!');
-
System.println("Hello, world!");
-
System.println(Hello, world!);
-
Out.system.println"(Hello, world!)";
答案:1
例子
下列语句的输出是什么?
System.out.println("\"Quotes\"");
System.out.println("Forward slashes \\//");
System.out.println("How '\"profound' \"\\\" it is!");
回答:
"Quotes"
Forward slashes \//
How '"profound' "\" it is!
实验室工作
以下程序的输出是什么?如果我们把next()
换成nextLine()
会怎么样?
public class TestScanner {
public static void main(String arg[]) {
Scanner sc=new Scanner(System.in);
System.out.println("enter string for c");
String c=sc.next();
System.out.println("c is "+c);
System.out.println("enter string for d");
String d=sc.next();
System.out.println("d is "+d);
}
}
问题
-
以下语句的输出是什么?
System.out.println("name\tage\theight"); System.out.println("Anthony\t17\t5'9\""); System.out.println("Belly\t17\t5'6\""); System.out.println("Bighead\t16\t6'");
-
以下语句的输出是什么?
System.out.println("\ta\tb\tc"); System.out.println("\\\\"); System.out.println("'"); System.out.println("\"\"\"");
-
用 Java 写一个程序打印以下内容:
\/ \\// \\\///
八、循环结构——for
循环
简单来说,循环结构重复做一些事情,直到状态改变(见图 8-1 )。
图 8-1
for 循环结构
例子
这里有一个例子:
for (int i = 0; i < 100; i++) {
<do something>
}
for
陈述中有三个关键要素:
-
int i = 0
:声明一个计数器变量,并给它赋一个初始值; -
i < 100
:定义继续for
循环的条件;只要这个条件为真,循环就会运行,当它不为真时,我们停止并退出for
循环; -
i++
:递增计数器值;i++
和i = i + 1.
是一回事
那么,“
实验室工作
-
使用
for
循环输出“Hello!”10 次。 -
使用
for
循环打印出从 1 到 25 的所有整数,包括 1 和 25。 -
打印出从 1 到 25 的所有整数。
-
输出从 3 到 99 的所有偶数。
for 循环公式
for
循环背后的数学模型实际上是一个算术序列:
for (int counter=firstTerm;
counter <= lastTerm;
counter=counter + difference) {
......
}
counter
系列中的第 n 项等于:
- 第一项+差值×(n–1)
求算术数列的“for 循环”公式
例如,下面是一个数字列表:
-
-4, 5, 14, 23, 32, 41, 50, 59, 68, 77, 86.
-
它遵循一个算术序列。
-
firstItem = -4
-
lastItem = 86
-
差值= 5-(-4)= 9
-
将此转化为一个
for
循环:
for(int i=-4; i <= 86; i=i+9) { ...... }
它将遍历列表中的每一个数字。
数学:战略计数
你可以用手指数,但是当你的数列中有非常多的数字时,那就没用了。正确的做法是通过重组来准备这些数字。目的是找到一个好的模式,以便我们可以系统地计数。
在这里寻找一个模式就是找出一个代表数列中每个数字的基本公式。
看看这个例子:3,4,5,6,....,100,所以我们知道数字的总数是:
- 100 – 3 + 1 = 98.
一种常见的方法是将数列转换成更简单的形式。如果我们从数列的每个数字中减去 2,我们得到 1,2,3,4,......, 98.我们现在知道总数是 98。并且,表示每个数的公式将是 x(i) = i + 2,(i=1,2,......,98).
那 5,8,11,14 呢,......, 101?怎么用“for
循环”把它打印出来?
它看起来比上一个更复杂,但您可以尝试相同的方法。
-
每个数字减 5;它变成了 0,3,6,9,......, 96
-
除以 3,它就变成 0,1,2,3,......, 32
-
从 0 开始,一个一个数,一直数到 32,并不难。总数是 33。
-
数列中第 I 个数的通称将是 x(i) = 3 * i + 5,(i=0,1,2,......, 32).
现在,回到for
循环结构,很明显答案应该是这样的:
for (i=0; i <= 32; i++) {
System.out.println(3 * i + 5);
}
实验室工作
-
编写一个
for
循环,生成以下数字列表:1 4 9 16 25 36 49 64 81 100
(提示:寻找一个共同的模式。)
例子
以下循环序列的输出是什么?
它打印出以下内容:
****!****!****!
****!****!****!
外部for
循环(标记为“1”)有两次迭代,因此输出将有两行,由println()
。
中间的for
循环(标记为“2”)有三次迭代,所以它将打印出 2 x 3 = 6“!”总计通过print()
。
内部的for
循环(标记为“3”)有四次迭代,所以print()
总共会打印出 2×3×4 = 24 个*
。
实验室工作
- 写一个方法
exp()
来计算一个指数结果,给定一个基数和一个幂(也叫指数)。例如,exp(3, 4)
返回 81。限制是基数和指数不能为负。
问题
-
以下循环序列的输出是什么?
for (int i = 1; i <= 2; i++) { for (int j = 1; j <= 3; j++) { System.out.print(i + ""*"" + j + ""= "" + i * j + ""; "); } System.out.println(); }
-
编写一个
for
循环,生成以下数字列表:5 10 17 26 37 50
-
编写一个
for
循环,生成以下数字列表:1 8 27 64 125
-
编写一个
for
循环,生成以下数字列表:-1 0 7 26 63 124
-
使用
for
循环产生以下输出:***** ***** ***** *****
-
编写
for
循环代码,输出以下内容:one
Twenty-two
Three hundred and thirty-three
Four thousand four hundred and forty-four
九、循环结构——while
循环
这是循环结构的另一种方式(图 9-1 )。
图 9-1
while 循环
while(i == 0) {
<do something>; //the variable "i" may be updated in this code block.
}
可以在“做某事”过程中更新状态。
问:如果状态永远不变,会发生什么?
答:这将是一个“无限循环”,意味着程序将永远运行,直到它崩溃或被用户终止。
注意
你可能注意到了<do something>
线旁边的//
。这标识了开发人员留下的注释。你应该使用注释来注释你的代码,以便将来的读者更容易理解(他们可能是你,所以善待你未来的自己)。编译 Java 程序时,编译器会忽略所有注释。
例子
循环会执行它的主体多少次?
int x = 1;
while (x < 100) {
System.out.print(x + " ");
x += 10;
}
答案:十次(当 x = 1,11,21,31,41,51,61,71,81,91 时)
例子
循环会执行它的主体多少次?
int max = 10;
while (max < 10) {
System.out.println("count down: " + max);
max--;
}
答案:零。
for
循环和while
循环都是完成重复工作的循环结构(即<做某事>)。for
循环提供了一种简单的方法来分配初始计数器值,并定义计数器值如何在同一行代码中递增(或递减),而while
循环要求用户在单独的行中定义它们。以下两个示例具有相同的功能。
for(int i = 0; i < 10; i++) {
<do something>
}
int i = 0;
while(i < 10) {
<do something>
i++;
}
除了for
循环之外,我们需要while
循环选项还有一个很好的理由。这个例子显示了我们更喜欢while
循环而不是for
循环的许多情况之一。
boolean flag = true;
while(flag) {
< commit planned operations
, during which time the flag may be updated upon a certain codition change, e.g. the operation is completed, or failed for some reason.>
}
do-while 循环
Java 还提供了一个do
- while
循环结构:
do {
<do something>
} while (expression);
do
- while
和while
的区别在于do
- while
在循环的底部而不是顶部对其布尔表达式求值。因此,do
块(又名
class DoWhileDemo {
public static void main(String[] args){
int count = 1;
do {
System.out.println("Count is: " + count);
count++;
} while (count < 1);
}
}
实验室工作
-
使用
while
循环输出“Hello!”10 次。 -
使用
while
循环打印出从 1 到 25 的所有整数,包括 1 和 25。 -
解释下面的代码片段试图做什么。
int n = 5; while (n == 5) { n = n + 1; System.out.println(n); n--; }
问题
-
循环会执行它的主体多少次?
int x = 250; while (x % 3 != 0) { System.out.println(x); }
-
循环会执行它的主体多少次?
int x = 2; while (x < 200) { System.out.print(x + " "); x *= x; }
-
循环会执行它的主体多少次?
String word = "a"; while (word.length() < 10) { word = "b" + word + "b"; } System.out.println(word);
-
循环会执行它的主体多少次?
int x = 100; while (x > 1) { System.out.println(x / 10); x = x / 2; }
-
给定静态方法
runWhileLoop()
,当x = 10
时它的输出是什么?您可能希望将该方法复制到您的测试类中进行尝试。public static void runWhileLoop(int x) { int y = 1; int z = 0; while (2 * y <= x) { y = y * 2; z++; } System.out.println(y + " " + z); }
十、逻辑控制结构
与我们在进行逻辑对话时口头描述的方式非常相似,编程语言中的if
和if
/ else
是进行条件决策和选择相应执行路径的常见结构(图 10-1 )。
图 10-1
if 结构
if (a == 3) {
<do something>
}
这类似于但不完全相同,因为:
if (a == 3) {
<do something>
} else {
<do something else>
}
图 10-2 是if
/ else
逻辑控制结构的整个工作流程。
图 10-2
if/else 结构
条件运算符
下表中列出的条件运算符常用于条件语句中。比如我们用a == 3
来评价“是否a
等于3
?”在条件语句中。Java 使用六种不同的条件运算符来表达两个操作数之间的关系。表达式的结果为真或假,这是一个布尔值,它决定了“是”或“否”的执行路径。
条件运算符
|
描述
|
| --- | --- |
| == | 等于 |
| > | 大于 |
| >= | 大于或等于 |
| < | 小于 |
| <= | 小于或等于 |
| != | 不等于 |
例子
以下哪个if
语句头使用了正确的语法?
-
如果 x = 10,则
-
如果(x 等于 42) {
-
if (x => y) {
-
如果[x == 10] {
-
if (x == y) {
回答
e
例子
给定下面的方法,whatIsIt(9, 4)
的输出是什么?
public static void whatIsIt(int x, int y) {
int z = 4;
if (z <= x) {
z = x + 1;
} else {
z = z + 9;
}
if (z <= y) {
y++;
}
System.out.println(z + " " + y);
}
回答
10 4
实验室工作
-
定义一个整数变量,并将值“3”赋给它。
-
当整数变量被赋值为数字 3 时,使用
if
语句输出“Hello”。 -
当整数变量被赋值为 3 以外的任何数字时,使用
if
/else
语句输出“Goodbye”。 -
下面的代码有什么问题吗?
int n = 4; if (n >= 3) { System.out.println("Hello!"); } if (n == 4) { System.out.println("Hello again!"); }
-
使用
if
/else
语句实现以下要求:-
当数字小于 3 时,输出“小于 3”
-
当数字为 3 时,输出“等于 3”
-
当数字大于 3 时,输出“大于 3”
-
-
输入一个整数,然后,
-
当输入的数字大于 6 时,输出“数字大于 6”
-
当输入的数字小于 6 时,输出“数字小于 6”
-
-
解释下面的代码片段试图做什么:
Scanner scan = new Scanner(System.in); int n = scan.nextInt(); if (n > 6) { if (n > 8) { System.out.println("n is greater than 8"); } else { System.out.println("n is greater than 6, but n is smaller than 9"); } }
有时,您可能需要使用多个“真或假”条件的逻辑组合。让我们在这里引入另一个概念,即“逻辑运算符”
逻辑运算符
数学:逻辑运算符
逻辑运算符和逻辑运算:
-
&& 与关系
-
|| 或关系
-
!没有关系
-
A && B 表示只有当 A 和 B 都为真时,结果才为真。比如在(x>3&x<5)中,A 是“x > 3”,B 是“x < 5”。
-
A || B 表示当 A 或 B 为真时,结果为真。
-
!A 表示当 A 为真时,结果为假;当 A 为假时,结果为真。“的例子中(x > 0)”,A 是“x > 0”。
在所有这些例子中,A 和 B 是表达式或布尔变量。
| (甲和乙) | a =真 | a =假 | | b =真 | 真实的 | 错误的 | | b =假 | 错误的 | 错误的 |Summary -只有当 A 和 B 都为真时,结果才为真。否则,结果为假。
| (甲||乙) | a =真 | a =假 | | b =真 | 真实的 | 真实的 | | b =假 | 真实的 | 错误的 |只有当 A 和 B 都为假时,Summary - Result 才为假。否则,结果为真。
这些运算符的一些属性的最后一个示例:
-
(x< 0 || x >0)T2【x!= 0)
-
!(x == 0 || y == 0)等价于(x!= 0 && y!= 0)
-
!(x > 3 && x < 5) is equivalent to (x > = 5 || x <=3)
使用文氏图将有助于我们分析某些类型的逻辑问题。
数学:分析逻辑问题
维恩图是揭示数据集之间逻辑关系的可视化方法。
在图 10-3 中,圆 A 和圆 C 的重叠区域在区域 b。
如果我们定义 A = { x,y | x = 0 },C = { x,y | y = 0 },那么 B = { x = 0;y=0 }。
图 10-3
维恩图
实验室工作
-
算出下面程序的输出。
public class LogicalOperation { public static void main(String args[]) { boolean a = true; boolean b = false; System.out.println("a && b = " + (a&&b)); System.out.println("a || b = " + (a||b) ); System.out.println("!(a && b) = " + !(a && b)); } }
-
编写一个名为
quadrant
的静态方法,该方法将一对表示笛卡尔坐标系上的(x,y)点的整数作为参数。它返回该点的象限号(即 0,1,2,3,4,见图)。
下面是对该方法的示例调用。
|打电话
|
返回值
|
| --- | --- |
| quadrant(12, 17)
| 1
|
| quadrant(-2, 3)
| 2
|
| quadrant(-15, -3)
| 3
|
| quadrant(4, -42)
| 4
|
| quadrant(0, 3)
| 0
|
问题
-
将下列英语语句翻译成逻辑表达式:
-
z 是奇数。
-
x 是偶数
-
y 为正。
-
x 或者 y 是偶数。
-
y 是 z 的倍数。
-
z 不为零。
-
y 是一个正数,y 在数量级上大于 z。
-
x 和 z 是相反的符号。
-
y 是一个非负的一位数。
-
z 是非负的。
-
-
给定以下变量声明:
int x = 4; int y = -3; int z = 4;
下列表达式的结果是什么(对或错)?
x == 4 x == y
x == z y == z
x + y > 0 x - z != 0
y * y <= z y / y == 1
x * (y + 2) > y - (y + z) * 2
十一、错误和提示
以下是初学者容易犯的常见编码错误列表。如果你留意这些错误模式,它将帮助你克服最初的编码障碍。
-
缺少半个花括号
{}
。(它应该总是有一对。)
-
缺少括号
()
的一半。(它也应该总是有一双。)
-
每行结尾缺少分号
;
。 -
在条件检查中使用一个
=
符号。(正确的做法是
==
。) -
给未定义的变量赋值。
(正确的做法是,只有在变量被定义后,才给变量赋值。)
-
多次定义同一个变量。
int i; ...... int i = 3; ......
-
忘记在循环结构中递增/递减计数器。
-
main
函数的签名不正确。应该是
public static void main(String[] args
,所以注意public static void main
,还有String[] args
。 -
控制台窗口无输出–缺少输出行,例如
System.out.println()
。 -
变量名和字符串的区别:
- 变量名
a
是一个字符串。
- 变量名
-
变量名
a
是一个值为“a”的字符串。
string a;
-
Mistakenly resetting value in an aggregator:
for (int i=1; i<n; i++) { int sum = 0; sum += i; }
这个程序对从 1 到 n 的所有数字求和。为了纠正这个错误,需要将行
int sum = 0
移出循环结构,就在行for
之前。
string a = "a";
编程技巧
-
如何在文本编辑器上增大/减小字体大小:
使用 Ctrl +或 Ctrl -。
在 macOS 上是⌘+和⌘-
设置:首选项= >常规= >键
-
如何在 Eclipse 中注释掉一段代码:
选择代码块
按 CTRL 和“/”(同时)
在 macOS 上,它是命令+“/”
-
如何在代码块中添加注释:
You may use the Java Comments feature to briefly explain what a specific line of code or a code block in your program is doing so that other people can understand your implementation ideas. There are basically two ways you can write Java Comments among lines of code.
-
使用“//”作为前缀在一行中编写语句,例如:
// count 是跟踪点击总数的变量
int count = 0;
-
使用“/∫”和“∫/”来写多行注释,例如:
/* *
这段代码试图从指定的一组产品中找出最高价格值(以美元为单位):
*/
-
-
编码时注意“彩色下划线”:
红线–错误信息:语法等。
橙色线-警告信息
处理异常
到目前为止,在这一章中,我们已经解释了如何避免犯编译错误检测过程会发现的错误。运行时的那些错误呢?在 Java 中,我们使用下面的结构来捕获它们,并分别处理这些条件。这被称为异常处理。
try {
<main instruction code to execute>
......
} catch(IllegalArgumentException ex) {
<exception handling steps>
}
运行时可能导致错误的代码放在try
块中,运行时响应错误的代码放在catch
块中。这里的catch
块只响应抛出IllegalArgumentException
的错误。您可以指定多个catch
块来响应运行时抛出的不同类型的异常,因为您可能希望对不同类型的运行时错误做出不同的响应。最后,如果您的代码在运行时检测到一些问题,您可以故意抛出一个错误。你将在第十七章中看到如何做到这一点。
问题
-
下面的程序包含三个错误。纠正错误并提交程序的工作版本。
public MyProgram { public static void main(String[] args) { System.out.println("This is a test of the") System.out.Println("alarming system."); System.out.printLn("Thank you for your attention!") } }
-
下面的程序包含四个错误。纠正错误并提交程序的工作版本。
public class FriendMessage { public static main(string[] args) { System.out.println("Speaking plum"); System.out.println("and eat); }
-
下面的程序包含至少 10 个错误。纠正错误并提交程序的工作版本。
public class Many Errors { public static main(String args) { System.println(Hello, buddy!); message() } public static void message { System.out println("This program cannot "; System.out.println("have any "errors" in it"); }
十二、Java 基础概要
在这一章中,我们将总结到目前为止我们在 Java 基础领域所学的知识,并指出一些常见的错误。
通则
如何定义变量名
变量名字符串
-
大小写敏感的
-
可以包含数字 0 到 9
-
可以有下划线
_
或美元符号$
变量名字符串:
-
不能以数字开头
-
不能使用保留字,如
for
、class
、void
、if
、else
等。
如何在控制台中输出
-
System.out.print(<string + value>);
-
System.out.println(<string + value>);
如何在控制台中收听输入
Scanner input = new Scanner(System.in);
input.nextLine();
input.next();
input.nextInt();
input.nextFloat();
如何重复手术
for(<initial state>; <condition check>; <increment/decrement count>) {
<do something>;
}
while(<condition check>) {
<do something>
}
或者,
do {
<do something>
}
while(<condition check>)
如何控制条件操作
if (<condition check>) {
<do something>;
}
if (<condition check>) {
<do something>;
} else {
<do something else>;
}
if (<condition check>) {
<do something>;
}
else {
<do something else with the nested if/else statement(s)>;
}
基本编码结构
public class MyClass {
public static void main(...) {
myMethod();
}
private static void myMethod(...) {
for (... ; ... ; ...) {
......;
}
if (...) {
......;
}
else {
......;
}
}
}
花括号
-
总是带着一对花括号:“
{......}
” -
总是以“{”开头,然后是“}”
-
常见模式(以两对开/关为例)
-
{ { } } 开,开,关,关
-
{ } { } 开,关,开,关
-
{ } } { 错了!
-
} { } { 错了!
-
-
这些模式的基本规则是什么?
-
开始时,以“{”开头
-
最后,以“}”结尾
-
“}”不会多于“{”
-
实验室工作
-
以下程序的输出是什么?
public class StoryOfMethods { public static void main(String[] args) { method1(); method2(); System.out.println("Done with main."); } public static void method1() { System.out.println("This is from method1."); } public static void method2() { System.out.println("This is from method2."); method1(); System.out.println("Done with method2."); } }
-
以下程序的输出是什么?
public class OrderOfFunctions { public static void main(String[] args) { second(); first(); second(); third(); } public static void first() { System.out.println("Inside the first function."); } public static void second() { System.out.println("Inside the second function."); first(); } public static void third() { System.out.println("Inside the third function."); first(); second(); } }
十三、Java 基础项目
- 编写代码打印出下图。
- 编写代码来绘制以下形状。
- 写代码做这个三角形。
-
写一个函数来检查一个整数是否能被另一个整数整除。
例如:
(1)给定输入 10,5,输出为“是,2”;
(2)给定输入 11,2,输出为"否";
-
写 Java 代码画圣诞树。
-
写代码找出给定正整数的所有因子。
例如,当用户输入“10”时,你的程序应该输出 1,2,5,10
-
编写一个方法,该方法接受一个月(即 1 到 12 之间的整数)作为参数,并返回当前年份中该月的天数。
-
编写代码,生成从控制台输入的两个数字的乘积:
请输入两个数进行乘法运算
下一个数字- > 7
下一个数字- > 15
产品= 105
-
写一个检查正整数是否是质数的方法。
-
编写一个方法,将一个整数转换成用二进制表示的字符串。例如,给定一个参数“19”,它应该返回“10011”。
十四、Java 基础解决方案
以下是本书 Java 基础部分前几章的解决方案。
第章第五章:变量
-
数字电子设备的信号(开/关)
-
OOP 基于类;国际网络路跑联盟
-
(e)
-
(b)
第七章:输入和输出
-
name age height Anthony 17 5'9" Belly 17 5'6" Bighead 16 6'
-
a b c \\ ' """
第八章:循环结构–For 循环
-
内部循环有三次迭代,没有回车;外部循环有两次迭代。
-
减去一个后,你会发现一个清晰的模式。
-
立方数模式
-
所有数字加 1
-
嵌套循环
第九章:循环结构–While 循环
-
永远(死循环)
-
三次,x = 2,4,16
-
五次,最终输出是“bbbbbabbbbb”
-
六次,当 x = 100,50,25,12,6,3
-
8 3 ←中间有个空格
第十章:逻辑控制结构
十五、莱特兄弟的掷硬币游戏
编程帮助我们理解和解释许多复杂的问题。你可以找到一个有趣的在线视频,“抛硬币难题”,它讲述了一个历史故事,并用分析方法解释了一个概率问题的解决方案。这个故事是关于莱特兄弟,奥维尔和威尔伯,他们玩抛硬币游戏来决定谁应该首先开始新的飞行实验。他们连续掷硬币,直到奥维尔连续得到双头,或者威尔伯在“相邻”序列中得到一个头和一个尾。虽然视频使用了概率和代数概念来计算莱特兄弟的获胜优势,但我们将尝试使用 Java 编程进行实验。下面的方法模拟了莱特兄弟的游戏,并分析了他们的结果(注意有用的注释,用//
标记,这样我们可以更容易地阅读代码)。
private static int count_a, count_b, count_ab = 0;
private static int totalsteps_a, totalsteps_b = 0;
/// whoever gets below pattern first wins, or tie if both of them reach targeted patterns at the same round
/// a: HH wins; b: HT wins; Use boolean 'true': head, 'false': tail
public static void flipCoin()
{
Random r = new Random();
/// initial value, or first round result
boolean current_a = r.nextBoolean();
boolean current_b = r.nextBoolean();
boolean win_a = false;
boolean win_b = false;
int round = 1;
while(true) {
round++;
boolean next_a = r.nextBoolean();
boolean next_b = r.nextBoolean();
if (current_a && next_a) {
win_a = true;
}
if (current_b && !next_b) {
win_b = true;
}
if (win_a && win_b) {
System.out.println("Both WIN! - round: " + round);
count_ab++;
totalsteps_a += round;
totalsteps_b += round;
break;
}
if (win_a && !win_b) {
System.out.println("A WIN! - round: " + round);
count_a++;
totalsteps_a += round;
break;
}
if (!win_a && win_b) {
System.out.println("B WIN! - round: " + round);
count_b++;
totalsteps_b += round;
break;
}
current_a = next_a;
current_b = next_b;
}
}
使用下面的main
方法,我们可以收集样本并得到统计摘要。
public static void main(String[] args) {
final int MAX = 10000;
for(int i=0; i < MAX; i++) {
flipCoin();
}
System.out.println("Summary");
System.out.println("Total samples: " + MAX);
System.out.println("Winning counts: a - " + count_a + "; b - " + count_b + "; ab - " + count_ab);
int probability_a = count_a * 100 / (count_a + count_b);
int probability_b = count_b * 100 / (count_a + count_b);
System.out.println("Winning probability: HH=" + probability_a + "%; HT=" + probability_b + "%.");
double average_a = totalsteps_a / (count_a + count_ab);
double average_b = totalsteps_b / (count_b + count_ab);
System.out.println("Average rounds to win: HH=" + average_a + "; HT=" + average_b + ".");
}
在我们用不同的参数运行了许多实验之后,我们了解了实际发生的情况,然后可以得出结论,威尔伯赢得赌注的机会明显更高(大约 62%比 37%)。输出应该如下所示。
.........
B WIN! - round: 3
A WIN! - round: 2
A WIN! - round: 3
B WIN! - round: 2
B WIN! - round: 4
A WIN! - round: 4
B WIN! - round: 2
B WIN! - round: 2
B WIN! - round: 3
B WIN! - round: 2
A WIN! - round: 2
A WIN! - round: 2
Both WIN! - round: 2
B WIN! - round: 3
Summary
Total samples: 10000
Winning counts: a - 3213; b - 5386; ab - 1401
Winning probability: HH=37%; HT=62%.
Average rounds to win: HH=2.0; HT=3.0.
十六、毕达哥拉斯三元组
勾股定理在小学到中学的学生中是众所周知的,因为它看起来很优雅,适用于所有的直角三角形。
数学:毕达哥拉斯三元组
在直角三角形内,a 和 b 是两条边,c 是斜边。
- a2+b2= c2
当 a,b,c 是满足勾股定理的正整数时,(a,b,c)称为“勾股三元组”。显然(3,4,5)是第一个毕达哥拉斯三元组,接下来是(5,12,13),(6,8,10),以此类推。毕达哥拉斯三元组的数目是无限的。
例子
那么,我们如何找到所有低于 100 的毕达哥拉斯三元组呢?
回答
我们可以将(3,4,5)乘以任意整数得到(6,8,10),(9,12,15),…,(57,76,95)。我们可以用(5,12,13)作为另一个基三元组得到(10,24,26),…,(35,84,91)。等等。
但是这种方法将要求我们首先找出所有的基本毕达哥拉斯三元组。因此,我们基本上必须检查 a 的每个小于 100 的正整数,然后计算 b 和 c,假设 a < b < c。顺便说一下,a = b 是不可能的。然而,有了编程方法,它不再是一个具有挑战性的数学问题。
这是找出满足勾股定理的所有可能三元组(a,b,c)的方法。
private static int allPythagoreanNumbers(int upperBound) {
int count = 0;
for(int a = 1; a < upperBound; a++) {
for(int b = a; b < upperBound; b++) {
for(int c = b; c < upperBound; c++) {
if (a * a + b * b == c * c) {
System.out.println("("+a+", "+b+", "+c+")");
count++;
}
}
}
}
return count;
}
public static void main(String[] args) {
System.out.println("Total count: " + allPythagoreanNumbers(100));
}
它将输出如下内容:
-
(3, 4, 5)
-
(5, 12, 13)
-
(6, 8, 10)
-
(7, 24, 25)
-
(8, 15, 17)
-
(9, 12, 15)
-
(9, 40, 41)
-
(10, 24, 26)
-
(11, 60, 61)
-
(12, 16, 20)
-
(12, 35, 37)
-
(13, 84, 85)
-
(14, 48, 50)
-
(15, 20, 25)
-
(15, 36, 39)
-
(16, 30, 34)
-
(16, 63, 65)
-
(18, 24, 30)
-
(18, 80, 82)
-
(20, 21, 29)
-
(20, 48, 52)
-
(21, 28, 35)
-
(21, 72, 75)
-
(24, 32, 40)
-
(24, 45, 51)
-
(24, 70, 74)
-
(25, 60, 65)
-
(27, 36, 45)
-
(28, 45, 53)
-
(30, 40, 50)
-
(30, 72, 78)
-
(32, 60, 68)
-
(33, 44, 55)
-
(33, 56, 65)
-
(35, 84, 91)
-
(36, 48, 60)
-
(36, 77, 85)
-
(39, 52, 65)
-
(39, 80, 89)
-
(40, 42, 58)
-
(40, 75, 85)
-
(42, 56, 70)
-
(45, 60, 75)
-
(48, 55, 73)
-
(48, 64, 80)
-
(51, 68, 85)
-
(54, 72, 90)
-
(57, 76, 95)
-
(60, 63, 87)
-
(65, 72, 97)
-
总数:50
这只是我们如何使用程序解决问题的众多演示之一。
问题
-
在示例中,我们使用三个
for
-循环从 1 到 99 迭代 a、b、c。你如何通过减少到两个for
-循环来改进它? -
利用例子中的思想,我们如何找出所有小于 100 的毕达哥拉斯素数?毕达哥拉斯素数解释如下。
数学:毕达哥拉斯质数
毕达哥拉斯素数是两个平方的和。并且,它需要是 4n + 1 的形式,其中 n 是正整数。毕达哥拉斯素数的例子有 5,13,17,29,37 和 41。
印度人
利用示例代码,看看如何进行小的更改来找到解决方案。
十七、强类型编程
正如我们在本书第一部分开始时了解到的,Java 编程语言已经定义了整数、双精度、布尔、字符串等。,作为基本类型。在 Java 中,如果没有预先定义变量的类型,我们就不能给变量赋值。只有在一个变量被一个类型明确定义后,例如,integer,我们才被允许给它分配一个整数值,并开始在计算中使用它作为一个整数。从逻辑上讲,一旦定义了变量的类型,就不能再给它赋不同类型的值。例如,如果一个变量被定义为布尔值,它就不能被赋予整数值。否则,我们将得到一个类型不匹配的编译错误。
铅字铸造
但是,如果一个变量被定义为 double,我们怎么给它赋一个整数值呢?让我们做一些实验。
public static void main(String[] args) {
double a = 5;
System.out.println(a);
double b = 3 * 5;
System.out.println(b);
double x = 5 / 3;
System.out.println(x);
double y = (double)(5 / 3);
System.out.println(y);
double z = (double)5 / 3;
System.out.println(z);
double t = 5.0 / 3;
System.out.println(t);
double u = 5 / 3d;
System.out.println(u);
}
这是输出:
下面的模式是我们从这个实验中学到的:
-
当整数 5 被赋给双精度类型变量时,该变量将得到一个带小数点表示的等效值,即 double value 5.0。
-
当一个整数除以另一个整数时,结果遵循相同的整数类型,例如 5 / 3 = 1。但是,当分数“5 / 3”被赋给双精度类型变量时,结果值将自动转换为双精度值 1.0。
-
(double)(5 / 3)
将(5/ 3)
的整数结果转换为双精度值。这叫做类型转换。结果是 1.0,一个双精度值。
我们如何从 5 / 3 得出一个精确的值?诀窍是用(double)5 / 3
,而不是5 / 3
。(double)5 / 3
的结果是一个双精度值。或者,您可以使用 5.0 / 3 来产生相同的结果。另一种方式是5d / 3
,或5 / 3d
。两个表达式的结果都是相同的双精度值。
数学:直线的斜率
在 x-y 2D 笛卡尔坐标系中,点(x1,y1)和(x2,y2)之间的直线的斜率等于(y2 - y1) / (x2 - x1)。
例子
实现一个名为double getSlope()
的公共方法,该方法返回一条线的斜率。如果两个点有相同的 x 坐标,分母为零,斜率未定义,所以在这种情况下应该抛出一个IllegalArgumentException
。这将停止您的程序运行,并显示指定的错误信息。
回答
在一个Line
类中,我们定义了两个点和一个构造函数:
private Point p1;
private Point p2;
public Line(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
}
Point
类被设计为:
public class Point {
private int x;
private int y;
public Point() {
}
public void setX(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setY(int y) {
this.y = y;
}
public int getY() {
return y;
}
}
现在我们在Line
类中添加一个名为getSlope()
的方法。
public double getSlope() {
if (this.p1.getX() == this.p2.getX()) {
throw new IllegalArgumentException("Denominator cannot be 0");
}
return (double)(this.p2.getY() - this.p1.getY()) / (this.p2.getX() - this.p1.getX());
}
该方法看起来很容易,但棘手的部分是我们将两个整数相除的结果转换为 double 值,也就是说,
(double)(this.p2.getY() - this.p1.getY()) / (this.p2.getX() - this.p1.getX())
数学:共线性
如果可以画一条直线来连接点,那么这些点就是共线的。两个基本的例子是三个点具有相同的 x 或 y 坐标。更一般的情况可以通过计算每对点之间的直线的斜率并检查所有对点的斜率是否相同来确定。
我们使用公式(y2 - y1) / (x2 - x1)来确定两点(x1,y1)和(x2,y2)之间的斜率。
将以下方法添加到您的Line
类中:
public boolean isCollinear(Point p)
如果给定点与这条线的点共线,它需要返回 true。
十八、条件语句
如何识别和表示 x 和 y 这两个数中较大的那个数?
数学:假设和结论
在数学公式中,我们必须引入绝对符号来构成表达式:
x 和 y 之间较大的数字
回忆一下if
/ else
结构:
if (x >= y) {
// x is the bigger number
} else {
// y is the bigger number
}
它非常简单易懂。
if
/ else
结构遵循从假设到结论的常见实验。
if (<Hypothesis>) {
<Conclusion>
} else { // the hypothesis is NOT valid
<Different conclusion>
}
<Hypothesis>
部分需要是一个布尔值,在一个数学表达式中可以包含一个变量或多个变量。
条件语句有几种类型的结构(有些你已经见过,有些对你来说是新的)。
-
简单的
if
,或if
/else
子句。 -
稍微复杂一点的
if
/else if
阶梯。if (...) { ...... } else if (...) { ...... } else if (...) { ...... } else { ...... }
-
嵌套的
if
/else
语句。if (...) { ...... } else { if (...) { ...... } else { ...... ...... } }
最后一种模式用于实现树状结构。当我们决定使用哪种模式时,这将取决于我们所解决的问题的类型。
例子
下面这段代码有什么问题吗?
if (i > 50) {
<do something...>
} else if (i > 100) {
<do something...>
} else {
<do something...>
}
回答
当i <= 50
的时候,永远不会是i > 100
,所以中间的else if
分支其实是一条死路。一个简单的修正应该是交换代码中“50”和“100”的位置。而且注意这个,当它在下面的代码块中说else if (i > 50) {...}
的时候,实际上是指 50 < i < = 100。
if (i > 100) {
<do something...>
} else if (i > 50) {
<do something...>
} else {
<do something...>
}
例子
创建一种将学生的分数(0 到 100 的整数)映射到标准 GPA 分数的方法。
回答
第一种解决方案(v0
)使用了几个if
子句。问题是,例如,当分数为 69 时,它必须执行所有四个if
条款。这不是一个有效的方法。
public static char getGpaScore_v0(int points) {
if (points > 89) {
return 'A';
}
if (points < 90 && points > 79) {
return 'B';
}
if (points < 80 && points > 69) {
return 'C';
}
if (points < 70 && points > 64) {
return 'D';
}
// if (points < 65) <-- this line can be omitted
return 'F';
}
第二个解决方案(v1
)利用了一个嵌套的"if
/ else"
语句。它解决了从早期版本中观察到的问题- v0
。
public static char getGpaScore_v1(int points) {
if (points > 89) {
return 'A';
} else {
if (points > 79) {
return 'B';
} else {
if (points > 69) {
return 'C';
} else {
if (points > 64) {
return 'D';
} else {
return 'F';
}
}
}
}
}
为了给代码结构提供更好的可读性,引入了第三种解决方案(v2
),如下所示。
/*
* 90 to 100 --- A
* 80 to 89 --- B
* 70 to 79 --- C
* 65 to 69 --- D
* below 65 --- F
*/
public static char getGpaScore_v2(int points) {
if (points > 89) {
return 'A';
} else if (points > 79) {
return 'B';
} else if (points > 69) {
return 'C';
} else if (points > 64) {
return 'D';
} else {
return 'F';
}
}
数学:象限
在笛卡尔坐标系中,象限由 x 和 y 坐标是正数还是负数决定。有四个象限,由 x 轴和 y 轴分隔。具体来说,所有的点(x > 0,y > 0)都属于象限 I(或第 1 象限);所有的点(x < 0, y > 0)都属于第二象限(或第二象限);所有点(x < 0, y < 0) belong to quadrant III (or 3rd quadrant); and all the points (x > 0,y < 0)属于象限 IV(或第四象限)
例子
能不能写一个方法来识别任意给定点(x,y)在坐标系上属于哪个象限?x 和 y 都是实数。如果一个点落在 x 轴或 y 轴上,那么该方法应该返回 0。
回答
在这个例子中有两个变量,x
和y,
。将 x 和 y 定义为浮点数类型。进行如下所示的案例分析:
情况 1:当一个点落在 x 轴或 y 轴上时 y = 0 或 x = 0
情况 2:当一个点落在第一象限 x > 0,y > 0 时
情况 3:当一个点落在第二象限 x < 0,y > 0 时
情况 4:当一个点落在第三象限 x < 0,y < 0 时
情况 5:当一个点落在第四象限 x > 0,y < 0 时
将情况 2 和情况 5 组合成 x > 0 的类别,将情况 3 和情况 4 组合成 x < 0 的类别,产生如下代码结构:
private static int quadrant(float x, float y) {
if (x == 0 || y == 0) {
return 0;
}
else if (x > 0) // x > 0 and y <> 0
{
if (y > 0) {
return 1;
}
return 4;
}
else // x < 0 and y <> 0
{
if (y > 0) {
return 2;
}
return 3;
}
}
它使用float
来保存x
和y
值,尽管它也可以使用double
来这样做。float
和double
都是用于存储浮点数的数值数据类型。double
类型需要两倍于float
类型的空间,因为每种float
类型的数据用 32 位表示,而一种double
类型的数据使用 64 位
三元运算符
Java 使您能够直接从布尔表达式中赋值(真或假)。这被称为三元运算符。例如:
int a, b, max;
max = a < b? b : a;
这暗示着,当a < b
,max = b
;否则max = a
。
该语法保存一个if
/ else
语句。例如,我们可以使用下面的方法来获得绝对值:
public int getAbsolutionValue(int a) {
if (a < 0) {
return -a;
}
else {
Return a;
}
}
但是通过使用三元运算符,我们只用一行代码就可以完成:
a = a < 0 ? -a : a;
问题
-
请重写如下代码,以提高其逻辑性和可读性(
num
为整数值)。if (num < 10 && num > 0) { System.out.println("It's an one digit number"); } else if (num < 100 && num > 9) { System.out.println("It's a two digit number"); } else if (num < 1000 && num > 99) { System.out.println("It's a three digit number"); } else if (num < 10000 && num > 999) { System.out.println("It's a four digit number"); } else { System.out.println("The number is not between 1 & 9999"); }
-
Take the following three
if
statements:if (a == 0 && b == 0) {...} if (a == 0 && b != 0) {...} if (a != 0 && b != 0) {...}
请简化代码逻辑,将它们组合在一起。
十九、switch
语句
利用switch
条件语句代替if
语句有时可以呈现更清晰的代码逻辑。当我们有一个变量或一个包含变量的表达式,这些变量可能有不同的结果值,后面跟着不同的动作,这是一个使用switch
的好机会。
switch (<expression>) {
case <result 1>:
<action 1>;
break;
case <result 2>:
<action 2>;
break;
......
case <result n>:
<action n>;
break;
default:
<other action>;
break;
}
switch 语句的一个简单应用是在x = 1
、x = 2
或x = 3
、……的情况下采取不同的动作,如下所示:
switch (x) {
case 1: ...;
case 2: ...;
case 3: ...;
default: ...;
}
例子
编写一个方法,在给定整数值输入的情况下,用英语单词表达式打印出月份。
回答
我们可以使用if
/ else
梯形语句将一个整数转换成月份名称。
public static void tellNameOfMonthByIfElse(int month) {
if (month == 1) {
System.out.println("January");
} else if (month == 2) {
System.out.println("February");
} else if (month == 3) {
System.out.println("March");
} else if (month == 4) {
System.out.println("April");
} else if (month == 5) {
System.out.println("May");
} else if (month == 6) {
System.out.println("June");
} else if (month == 7) {
System.out.println("July");
} else if (month == 8) {
System.out.println("August");
} else if (month == 9) {
System.out.println("September");
} else if (month == 10) {
System.out.println("October");
} else if (month == 11) {
System.out.println("November");
} else if (month == 12) {
System.out.println("December");
} else {
System.out.println("Unknown month");
}
}
如果我们利用switch
条件语句来做同样的翻译,它也会工作得很好。
public static void tellNameOfMonthBySwitch(int month) {
String nameOfMonth;
switch (month) {
case 1: nameOfMonth = "January";
break;
case 2: nameOfMonth = "February";
break;
case 3: nameOfMonth = "March";
break;
case 4: nameOfMonth = "April";
break;
case 5: nameOfMonth = "May";
break;
case 6: nameOfMonth = "June";
break;
case 7: nameOfMonth = "July";
break;
case 8: nameOfMonth = "August";
break;
case 9: nameOfMonth = "September";
break;
case 10: nameOfMonth = "October";
break;
case 11: nameOfMonth = "November";
break;
case 12: nameOfMonth = "December";
break;
default: nameOfMonth = "Unknown month";
break;
}
System.out.println(nameOfMonth);
}
有一个更好的方法。不如我们定义一个数组来存储用英文表示所有月份的名称字符串列表,也就是数组nameOfMonth
。我们实际上是在整数(从 0 到 12)和月份名称字符串之间构建一个映射表。由于数组从索引 0 开始,我们有意将"none"
赋给数组中的第一个元素。
private static String[] nameOfMonth = new String[] {
"none", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
public static void main(String[] args) {
System.out.println(nameOfMonth[1]); // January
System.out.println(nameOfMonth[8]); // August
}
例子
给定两个整数输入:年和月,编写一个方法来返回一个月中的天数。
回答
使用switch
条件语句:
public static int tellNumberOfDaysByYearMonth(int year, int month) {
int numOfDays = 0;
switch (month) {
case 1: case 3: case 5:
case 7: case 8: case 10:
case 12:
numOfDays = 31;
break;
case 4: case 6:
case 9: case 11:
numOfDays = 30;
break;
case 2:
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
numOfDays = 29;
} else
numOfDays = 28;
}
break;
default:
break;
}
return numOfDays;
}
请注意,它有一个特殊的逻辑来处理闰年的二月。由于计算二月份总天数的复杂性,当我们将前面提到的静态数组方法应用于这种情况时,我们必须执行以下操作:
private static int[] numberOfDaysByMonth = new int[] {
0, // none
31, // January
28, // February
31, // March
30, // April
31, // May
30, // June
31, // July
31, // August
30, // September
31, // October
30, // November
31 // December
};
在使用整数数组之前,我们需要在运行时根据年份值修改二月的值:
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
numberOfDaysByMonth[2] = 29;
}
问题
使用 switch 条件语句编写以下代码。
char color ='C';
if (color=='R') {
System.out.println("The color is red");
}
else if(color=='G') {
System.out.println("The color is green");
}
else if(color=='B') {
System.out.println("The color is black");
}
else {
System.out.println("Some other color");
}
二十、追踪移动物体
Java 提供了一个基本的编码框架,比如for
或while
循环和if
或switch
条件语句。我们可以利用它们来记录移动物体的时间。首先,我们将处理一个常见的数学问题——弹跳球场景。
数学:弹跳球
在纯数学方法中,我们会建立一个表格来记录每次反弹后的高度。并没有那么难,但是如果我们改变了原始问题设置中的高度值,我们将不得不手动重新计算同一表格中的值。
例子
一个球从 3 米高处落下。在第一次弹跳时,它上升到 2 米的高度。它一直下落,并反弹到上次反弹时达到的 2/3 高度。在哪个弹跳上会上升到不到 0.5 米的高度?这个问题选自过去的 AMC 8(8 年级以下的美国数学竞赛)
回答
这种编程方法将大大减少重复性的手工工作。
public static void main(String[] args) {
System.out.println(ballBouncing(3.0));
}
private static int ballBouncing(double originalHeight) {
double currentHeight = originalHeight;
int count = 0;
while(currentHeight > 0.5) {
currentHeight = currentHeight * 2 / 3;
count++;
System.out.println("Bounce No=" + count +
"; current height=" + currentHeight);
}
return count;
}
当您执行它时,输出显示每次反弹后的当前高度。
Bounce No=1; current height=2.0
Bounce No=2; current height=1.3333333333333333
Bounce No=3; current height=0.8888888888888888
Bounce No=4; current height=0.5925925925925926
Bounce No=5; current height=0.3950617283950617
5
改变参数originalHeight
并重新执行相同的程序会迅速输出详细的结果。这比用传统的数学方法在纸上求解要有效得多。
例子
一只蜗牛试图从井里爬出来。每天它爬上井边 4 英尺,每晚它滑下井 2 英尺 6 英寸。如果蜗牛早上从 40 英尺深的地方开始,它需要多少天才能从井里出来?这个问题选自一个数学竞赛。
回答
为了保持使用整数值,我们通过计算将英尺转换为英寸。注意,我们通过使用关键字final
将井的深度设置为一个常量变量。我们在蜗牛每天爬上来之后,滑下去之前,检查它是否到达了井口。
private static void snail() {
final int DEPTH = 12 * 40;
int currentHeight = 0;
int numOfDays = 0;
while (currentHeight < DEPTH) {
currentHeight += 12 * 4;
numOfDays++;
if (currentHeight >= DEPTH) {
break;
}
currentHeight -= 12 * 2 + 6;
System.out.println("No. " + numOfDays + " day - " +
(DEPTH - currentHeight) + " inches to the top.");
}
System.out.println("No. " + numOfDays + " day - at the top.");
}
这是程序运行时的部分输出:
............
No. 1 day - 462 inches to the top.
No. 2 day - 444 inches to the top.
No. 3 day - 426 inches to the top.
No. 4 day - 408 inches to the top.
No. 5 day - 390 inches to the top.
No. 6 day - 372 inches to the top.
No. 7 day - 354 inches to the top.
No. 8 day - 336 inches to the top.
No. 9 day - 318 inches to the top.
No. 10 day - 300 inches to the top.
No. 11 day - 282 inches to the top.
No. 12 day - 264 inches to the top.
No. 13 day - 246 inches to the top.
No. 14 day - 228 inches to the top.
No. 15 day - 210 inches to the top.
No. 16 day - 192 inches to the top.
No. 17 day - 174 inches to the top.
No. 18 day - 156 inches to the top.
No. 19 day - 138 inches to the top.
No. 20 day - 120 inches to the top.
No. 21 day - 102 inches to the top.
No. 22 day - 84 inches to the top.
No. 23 day - 66 inches to the top.
No. 24 day - 48 inches to the top.
No. 25 day - at the top
.
二十一、计算
我们学习了许多数学方法来解决计数问题。其中有些问题需要对排列组合有深入的理解。在本章中,我们将学习如何使用编程解决计数问题的例子。
例子
公共汽车票价分别为 4 美元和 6 美元。总共卖出了 45 张票,赚了 230 美元。卖出了多少张 4 美元的票?(2007/MathIsCool 问题在 http://academicsarecool.com
)
回答
这可以通过单个循环来解决。我们将变量tickets
设置为$4.00 门票的数量。因为票的总数是 45,所以$4.00 的票的数量不能大于 45。所以,tickets
是 46 以下的整数。
private static void calculateBusTickets() {
for(int tickets = 0; tickets < 46; tickets++) {
int totalMoney = 4 * tickets + 6 * (45 - tickets);
if (totalMoney == 230) {
System.out.println(tickets + " $4.00 tickets were sold.");
break;
}
}
}
例子
椅子有 4 条腿,凳子有 3 条腿,桌子有 1 条腿。生日聚会,每桌 4 把椅子,总共 18 件家具。其中一个孩子数出总共有 60 条腿。有多少凳子?(2016/MathIsCool 问题在 http://academicsarecool.com
)
回答
在下面的方法中,变量tables
表示表格的数量。那么,椅子的数量是 4 × tables
,凳子的数量是(18—tables
—4 *tables
)。
private static void countFurniture() {
for(int tables = 0; tables < 19; tables++) {
if (tables + 4 * 4 * tables + 3 * (18 - tables - 4 * tables) == 60) {
System.out.println((18 - tables) + " stools.");
break;
}
}
}
例子
多项选择题考试由 20 道题组成。每个正确答案的得分为+5,每个错误答案的得分为-2,每个未回答的问题的得分为 0。约翰的考试分数是 48 分。他最多能答对多少个问题?(1987/AMC8 问题在 https://artofproblemsolving.com/wiki/index.php/1987_AJHSME
)
回答
与前两个例子不同,在这个例子中,我们将在循环中使用两个变量c
和w
。假设正确答案的数量是c
,错误答案的数量是w
。它们的总和不能大于 20,即问题总数。因为它可能有不止一个解,我们不使用break
在它找到第一个解后立即退出程序。这是一种不同于前面例子的方法。
private static void scoring() {
for(int c = 0; c < 20; c++) {
for(int w = 0; w < 20 - c; w++) {
if (5 * c - 2 * w == 48) {
System.out.println("Correct answers: " + c
+ "; wrong answers: " + w);
}
}
}
}
输出是:
Correct answers: 10; wrong answers: 1
Correct answers: 12; wrong answers: 6
例子
有多少个不同的四位数能被 3 整除,并且最后两位是 23?(2003/10B AMC 问题在 https://artofproblemsolving.com/wiki/index.php/2003_AMC_8
)
回答
我们需要注意这个问题中的措辞,“不同的四位数”。策略是将所有条件分成两部分。
-
4 位数能被 3 整除,最后两位是“23”。
-
四位数都不一样。
private static void countNumbers() {
int totalCount = 0;
for(int i = 1000; i < 10000; i++) {
if (i % 3 == 0 && i % 100 == 23) {
int firstDigit = i / 1000;
int secondDigit = i / 100 % 10;
if (firstDigit != secondDigit &&
firstDigit != 2 &&
firstDigit != 3 &&
secondDigit != 2 &&
secondDigit != 3) {
totalCount++;
System.out.println(i);
}
}
}
System.out.println("Total count = " + totalCount);
}
它的输出是:
1023
1623
1923
4023
4623
4923
5823
6123
6423
6723
7023
7623
7923
8523
9123
9423
9723
Total count = 17
我们使用一个if
子句来验证所有四个数字都是不同的。
或者,我们可以创建一个通用的方法来检查它。
if (isDistinct(firstDigit, secondDigit, 2, 3)) {
......
}
这是isDistinct(...)
的实现。
private static boolean isDistinct(int a, int b, int c, int d) {
if (a == b) {
return false;
} else if (a == c) {
return false;
} else if (a == d) {
return false;
} else if (b == c) {
return false;
} else if (b == d) {
return false;
} else if (c == d) {
return false;
} else {
return true;
}
}
countNumbers2()
中的一个改进版本将是:
private static void countNumbers2() {
int totalCount = 0;
for(int i = 1000; i < 10000; i++) {
if (i % 3 == 0 && i % 100 == 23) {
int firstDigit = i / 1000;
int secondDigit = i / 100 % 10;
if (isDistinct(firstDigit, secondDigit, 2, 3)) {
totalCount++;
System.out.println(i);
}
}
}
System.out.println("Total count = " + totalCount);
}
例子
露丝有 10 枚硬币,不是 5 分、10 分就是 25 分。她有 N 个五分镍币、D 个一角硬币和 Q 个二角五分硬币,其中 N、D 和 Q 都不相同,并且都至少是 1。令人惊讶的是,如果她有 Q 个五分镍币、N 个一角硬币和 D 个二角五分硬币,她会有同样多的钱。露丝有多少美分?(2012 年 http://academicsarecool.com
的 MathIsCool 问题)
回答
在以下解决方案中,将使用两个for
-回路。
private static void countCoins() {
for(int n = 1; n < 9; n++) {
for(int d = 1; d < 10 - n; d++) {
int q = 10 - n - d;
if (5*n + 10*d + 25*q == 5*q + 10*n + 25*d){
System.out.println((5*n+10*d+25*q)+" cents.");
System.out.println("N="+n+"; D="+d+"; Q="+q);
}
}
}
}
输出是:
155 cents.
N=1; D=5; Q=4
例子
三个朋友总共有六支一模一样的铅笔,每个人至少有一支铅笔。这种情况会以多少种方式发生?(2004 年 AMC8 题在 https://artofproblemsolving.com/wiki/index.php/2004_AMC_8
)
回答
我们使用两个for
-循环来模拟我们如何将六支相同的铅笔分配给三个人,分别用变量first
、second
、third
来表示。
private static void countWays() {
int count = 0;
for(int first=0; first <= 6; first++) {
for(int second = 0; second <= 6 - first; second++) {
int third = 6 - first - second;
if (first > 0 && second > 0 && third > 0) {
count++;
System.out.println("first=" + first + "; second=" + second + "; third=" + third); }
}
}
System.out.println("Total count=" + count);
}
输出是:
first=1; second=1; third=4
first=1; second=2; third=3
first=1; second=3; third=2
first=1; second=4; third=1
first=2; second=1; third=3
first=2; second=2; third=2
first=2; second=3; third=1
first=3; second=1; third=2
first=3; second=2; third=1
first=4; second=1; third=1
Total count=10
例子
七块不同的糖果被分在三个袋子里。红色的袋子和蓝色的袋子必须每人收到至少一块糖果;白色袋子可以保持为空的。有多少种可能的安排?(2010/10B AMC 问题在 https://artofproblemsolving.com/wiki/index.php/2010_AMC_10B
)
回答
首先,我们设计一个实验。在这个实验中,我们希望将七个不同的字符串(“A”、“B”、“C”、“D”、“E”、“F”、“G”)放入三个字符串数组中。这七根线代表七种不同的糖果。这三个数组分别代表红色、蓝色和白色的袋子。放置的顺序并不重要,但是我们需要确保在放置完成后只有最后一个数组可以为空。目标是找到不同位置的数量。
当我们将“A”放入三个数组中的一个时,我们需要一个 for 循环来处理三个不同的数组。然后我们需要另一个for
-循环来放置“B”,以此类推;我们总共需要七个 for 循环。在每个for
-循环中,我们将字符串追加到现有数组中,即bag[i]
。但是完成之后,我们需要将它从数组字符串的尾部移除,以便它尝试下一个选项。这就是我们使用bag[i].replace("A", "")
的原因。这同样适用于其他六根弦。
我们想出了一个“直截了当”但看起来很丑的版本,如下所示。
注意
包–红色:0,蓝色:1,白色:2;循环中的 3 代表三个字符串数组,即三个袋子。
/// BAG - red: 0, blue: 1, white: 2
private static void distributeCandy() {
int count = 0;
String[] bag = { "", "", "" };
for(int i=0; i < 3; i++) {
bag[i] += "A";
for(int j=0; j < 3; j++) {
bag[j] += "B";
for(int k=0; k < 3; k++) {
bag[k] += "C";
for(int l=0; l < 3; l++) {
bag[l] += "D";
for(int m=0; m < 3; m++) {
bag[m] += "E";
for(int n=0; n < 3; n++) {
bag[n] += "F";
for(int p=0; p < 3; p++) {
bag[p] += "G";
if(bag[0].length() > 0 && bag[1].length() > 0) {
count++;
System.out.println("Red=" + bag[0] + " Blue=" + bag[1] + " White=" + bag[2]);
}
bag[p] = bag[p].replace("G", ""); }
bag[n] = bag[n].replace("F", ""); }
bag[m] = bag[m].replace("E", ""); }
bag[l] = bag[l].replace("D", ""); }
bag[k] = bag[k].replace("C", ""); }
bag[j] = bag[j].replace("B", ""); }
bag[i] = bag[i].replace("A", ""); }
System.out.println("Total count: " + count);
}
代码结构看起来太复杂了。嵌套的for
-循环太多。一个更好的想法是应用递归方法来提高它的简单性。现在看新版本:
private static int count = 0;
private static String[] bag = { "", "", "" };
private static String[] CANDY = new String[] { "A", "B", "C", "D", "E", "F", "G" };
private static String RemoveLastChar(String s) {
if (s == null && s.length() < 1) {
System.out.println("Input string is invalid!");
return "";
}
return s.substring(0, s.length() - 1);
}
private static void distributeCandies_Recursive(int pointer) {
for(int i=0; i < 3; i++) {
bag[i] += CANDY[pointer];
if (pointer == CANDY.length - 1) {
if (bag[0].length() > 0 && bag[1].length() > 0) {
count++;
System.out.println("Red=" + bag[0] + " Blue=" + bag[1] + " White=" + bag[2]);
}
}
else {
distributeCandies_Recursive((pointer + 1));
}
bag[i] = RemoveLastChar(bag[i]);
}
}
然后,我们在执行的main
函数中包含下面两行。
distributeCandies_Recursive(0);
System.out.println("Total count: " + count);
例子
随机选择 1000 到 10000 之间的回文。被 7 整除的概率是多少?(2010/10B AMC 问题在 https://artofproblemsolving.com/wiki/index.php/2010_AMC_10B
)
回答
回文数字是一个从左到右和从右到左读起来一样的数字。
在isPalindrome()
方法中,我们反转数字串,并与原始数字串进行比较。如果反转后的字符串与原来的字符串相同,则被识别为回文字符串。" 8558 "它的反串“8558”和它自己是一样的。
我们引入StringBuffer
类来利用它的reverse()
方法。范围[1000,10000]内的每个数字在被传递给isPalindrome()
方法之前都被转换为字符串类型。该解决方案可以应用于任何范围的整数。
public static void main(String[] args) {
countDivisibility();
}
private static boolean isPalindrome(String numberStr) {
String reversed = new StringBuffer(numberStr).reverse().toString();
return reversed.equals(numberStr);
}
private static void countDivisibility() {
int count = 0;
int total = 0;
for(int i = 1000; i < 10001; i++) {
if(isPalindrome(Integer.toString(i))) {
total++;
if (i % 7 == 0) {
count++;
System.out.println(i);
}
}
}
System.out.println("Probability=" + count + "/" + total);
}
方法isPalindrome2()
中有一种不同的方式,它包含与isPalindrome()
方法相同的功能。除了使用StringBuffer
,你可以简单地比较每个字符的前半部分和后半部分。
private static boolean isPalindrome2(String s) {
int len = s.length();
for( int i = 0; i < len / 2; i++ ) {
if (s.charAt(i) != s.charAt(len - i - 1)) {
return false;
}
}
return true;
}
例子
随机选择一个以 10 为基数的三位数 n。n 的 9 进制表示和 11 进制表示都是三位数的概率是多少?(2003/10A AMC 问题在 https://artofproblemsolving.com/wiki/index.php/2003_AMC_10A_Problems
)
回答
十进制三位数的总数是 900。十进制的三位数 124 是九进制的 147,十一进制的 103;10 进制 720,9 进制 880,11 进制 5A5。
关键的方法是countBase10Numbers()
:
private static void countBase10Numbers() {
int count = 0;
for(int i = 100; i < 1000; i++) {
String base9Number = convertToBaseN(i, 9);
String base11Number = convertToBaseN(i, 11);
if (base9Number.length() == 3 &&
base11Number.length() == 3) {
count++;
System.out.println(i + " -> " + base9Number +
"; " + base11Number);
}
}
System.out.println(count + " out of " + (1000 - 100));
}
支撑方式是convertToBaseN()
。
private static String convertToBaseN(int base10, int n) {
if(n < 2 || n > 16) {
return "";
}
String baseN = myOneDigit[base10 % n];
base10 = base10 / n;
while(base10 > 0) {
baseN = myOneDigit[base10 % n] + baseN;
base10 = base10 / n;
}
return baseN;
}
myOneDigit
数组是:
private static String[] myOneDigit =
{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
它实际上相当于一个实现了switch
条件语句的方法:
private static String convertDigit(int digit) {
String s = "";
switch(digit) {
case 10:
s = "A";
break;
case 11:
s = "B";
break;
case 12:
s = "C";
break;
case 13:
s = "D";
break;
case 14:
s = "E";
break;
case 15:
s = "F";
break;
default:
s = Integer.toString(digit);
break;
}
return s;
}
显然,使用字符串数组的方法更简单。
问题
-
Richa 和 Yashvi 将和他们的学校一起去牙买加。他们计划参加一个儿童门票 1.5 美元、成人门票 4 美元的博览会。在特定的一天,2200 人进入交易会,并收取 5050 美元。有多少孩子参加了?(2017 MathIsCool)
-
在一次有 10 道题的数学竞赛中,一个学生答对一题得 5 分,答错一题得 2 分。如果奥利维亚回答了所有问题,她的分数是 29,那么她有多少个正确答案?(2002 年 AMC8)
-
有多少不超过 2001 的正整数是 3 或 4 的倍数而不是 5 的倍数?(2001 年 AMC10)
-
有多少个三位数的正数恰好包含两个不同的数字(例如,343 或 772,而不是 589 或 111)?(2006 MathIsCool)
-
丽贝卡去商店买了五株植物。如果商店出售三种类型的植物,她可以购买多少种不同的植物组合?(2005 MathIsCool)
二十二、因式分解
在学校数学中,我们通常遵循一个程序来寻找任何给定正整数的所有因子。这个过程叫做因式分解,根据整数有多大,需要相当多的计算。使用 Java 编程环境,让我们创建一个简单的程序来为我们做同样的工作。我们想写代码来找出任意正整数的所有因子。当用户输入“10”时,程序应该输出:1、2、5 和 10。作为第一步,我们需要定义过程,然后用 Java 代码实现它。
数学:寻找因子
回想一下我们在学校是如何手动寻找因子的,我们用一个整数从最小的(即“1”)到最大的(即给定的整数本身),一个一个地,来检查它是否能被给定的数整除。当答案是肯定的时候,我们知道这是一个因素。否则,我们跳过它,转到下一个数字。
我们创建下面的代码块来完成这个过程。我们将这个版本的代码标记为“v1”,并计划从这里进行改进。
private static int listFactors_v1(int n) {
int counter = 0;
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
if (counter > 0) {
System.out.print(", ");
}
System.out.print(i);
counter++;
}
}
System.out.println();
System.out.println("Number of factors: " + counter);
return counter;
}
main
方法将如下所示:
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int iterations = 0;
while (true) {
iterations++;
System.out.println("Enter an integer number:");
int k = input.nextInt();
if (k < 0) {
k = -k;
}
}
System.out.println("Number of factors: " + listFactors_v1(k));
input.close();
}
当您编译并执行代码时,您将看到如下输出:
Enter an integer number:
2018
1, 2, 1009, 2018
Number of factors: 4
不要认为我们有解决这个问题的完美方案。实际上,它远未完成。
数学:将问题减半
当我们迭代从 1 到 n 的每一个单个数字时,为了找到 n 的所有可能的除数,我们观察到任何大于 n/2 的整数都不会是 n 的除数,因此,我们只需要检查从 1 到 n/2,而不是 n,这个变化将节省程序中一半的迭代次数。因此,我们现在在下面的 2.1 版本中有了直接的改进。
private static int listFactors_v21(int n) {
int counter = 0;
for (int i = 1; i <= n / 2; i++) {
if (n % i == 0) {
if (counter > 0) {
System.out.print(", ");
}
System.out.print(i);
counter++;
}
}
System.out.println(", " + n);
counter++;
System.out.println("Number of factors: " + counter);
return counter;
}
除了算法的改变,我们还将删除一个if
子句,以稍微降低复杂性。然后我们会拿出下面的 2.2 版本,稍加修改。
private static int listFactors_v22(int n) {
System.out.print("1"); // "1" is always the 1st factor
int counter = 1;
for (int i = 2; i <= n / 2; i++) {
if (n % i == 0) {
System.out.print(", " + i);
counter++;
}
}
System.out.println(", " + n); // n is always the last factor
counter++;
System.out.println("Number of factors: " + counter);
return counter;
}
够好吗?其实不是。
数学:使用平方根
如果我们记得在数学中如何测试正整数是否为质数,我们只使用从 2,3 到 n 的平方根的整数。我们将在这里应用相同的逻辑来寻找一对因子,以减少迭代次数。
private static int listFactors_v31(int n) {
int counter = 0;
for (int i = 1; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
if (counter > 0) {
System.out.print(", ");
}
System.out.print(i);
counter++;
if (i != n / i) {
System.out.print(", " + n / i);
counter++;
}
}
}
System.out.println();
System.out.println("Number of factors: " + counter);
return counter;
}
目前的 3.1 版本显然更好,因为它只检查 n 的平方根,而不是 n/2。以 n=100 为例,现在我们从 1 到 10 进行检查,而不是从 1 到 50。显然,迭代次数的减少是显著的。但是我们很快发现了一个问题。当前解决方案的输出是:
因子列表并不像我们希望的那样按升序排列,只是因为它成对地打印出因子。为了解决这个问题,我们将创建两个字符串。一个字符串存储每对因子中较小的一个。另一个字符串存储每对中较大的一个。
private static int listFactors_v32(int n) {
String s1 = "1";
String s2 = Integer.toString(n);
int counter = 2;
for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
s1 += ", " + i;
counter++;
if (i != n / i) {
s2 = n / i + ", " + s2;
counter++;
}
}
}
System.out.println(s1 + ", " + s2);
System.out.println("Number of factors: " + counter);
return counter;
}
我们能从目前的 3.2 版本中做进一步的改进吗?答案还是肯定的。我们没有将每对除数中较小的数存储到字符串中,而是直接将其发送到控制台。这将节省一个字符串的内存空间。这是另一个“小”的变化,但当我们处理一个可能包含大量因素的大数字时,这可能是一个巨大的节省。我们最终登陆了 3.3 版:
private static int listFactors_v33(int n) {
String s = Integer.toString(n);
int counter = 2;
System.out.print("1");
for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
System.out.print(", " + i);
counter++;
if (i != n / i) {
s = n / i + ", " + s;
counter++;
}
}
}
System.out.println(", " + s);
System.out.println("Number of factors: " + counter);
return counter;
}
以下是我们为解决这一问题所做工作的总结:
-
我们将整数的实际上限从 n 减少到 n/2,然后减少到 n 的平方根。
-
我们避免了使用额外的字符串进行临时存储。
-
我们一直在思考如何根据三个基本规则来改进我们的实现:
-
采用我们所知的最佳算法;
-
优化代码,消耗更少的内存,运行更快;
-
编写易于理解的代码,以便将来维护。
-
所有这些努力都促成了一个优化良好的代码。这的确是编程的艺术。
二十三、PI 的探索性实验
为了监测自然环境变化对鱼类生命周期的影响,科学家们必须一直跟踪鱼类的数量。一个湖里有一种叫 AA 的鱼供研究。科学家们将一小群标记为 AA 的鱼放归湖中,它们的总数等于#(total _ labelled)。一段时间后,他们从湖中随机捕获一些鱼 AA 作为样本,其总数等于#(total_captured)。在这些样本鱼 AA 中,他们整理出标签鱼 AA,其总数等于#(labelled _ inter _ captured)。
数学:计算人口
假设所有的鱼,包括标记的和未标记的,均匀地分布在湖中,我们使用下面的公式计算出湖中鱼 AA 的当前数量。
#(total_captured) * #(total_labeled) / #(labeled_among_captured)
该公式以简单的比率形式表示。湖中分布越均匀的鱼 AA,公式产生的结果就越准确。它本质上是一种统计思想,使用少量样本数据来预测大图中可能很大的总数。它试图以最小的误差对不可测量的物体进行测量。
鱼类实验的本质是基于概率论。它也适用于许多其他有趣的问题领域。其中之一是计算圆周率:3.14159.........
例子
怎么做基本编程才能算出圆周率的值?
数学:概率论中的圆周率
我们将一个圆内接成一个正方形,在笛卡尔坐标平面中紧靠 x 轴和 y 轴。假设正方形的长度(即圆的直径)为 n,圆的面积可以用公式表示为:
- p × (n/2) 2
p 是要搞清楚的东西,就是圆周率。
如果在正方形内随机选择一个点,该点正好在圆内的概率是多少?
学过基本的几何概率就知道答案了。应该是圆和正方形的面积比。由于正方形的面积是 n 2 ,所以比例会是 p/4。
- p×(n/2)2:n2= p:4
回答
概率 p/4 表明,如果我们尽可能多地重复相同的点选择过程,圆内选择的点数与正方形内选择的点数之比将接近(并最终等于)p/4。因此,一旦我们找出比率,我们就知道圆周率的近似值。这是我们将在程序中使用的算法。
public static double computePi(int total, int n) {
int count = 0;
for(int i=0; i < total; i++) {
double x = n * Math.random();
double y = n * Math.random();
if ((x - n/2) * (x - n/2) + (y - n/2) * (y - n/2)
< (n/2) * (n/2)) {
count++;
}
}
return (double)count * 4 / total;
}
在这种方法中,
-
整数参数值
total
是实验的总数(即所选样本点的总数)。 -
整数参数值
n
是正方形的边长,或者圆的直径。 -
Math.random()
是来自java.util.Random
包的 Java 内置函数。它生成一个介于 0 和 1 之间的双精度随机数。乘以n
使 x 和 y 坐标值在 0 和n
之间。 -
不等式“(x-n/2)2+(y-n/2)2<(n/2)2”是检查该点是否位于圆心在(n/2,n/2)的圆内。
您可以通过跟随
main
方法()
中的行来调用该方法:
public static void main(String[] args) { int onehMillion = 100 * 1000 * 1000; for(int i=0; i < 10; i++) { System.out.println(computePi(onehMillion, 100)); } }
-
n = 100
是边长。它不会直接影响公式。您可以使用其他数字,如 10 或 2 或任何偶数(由于“n/2”),用于扩展实验目的。 -
int onehMillion = 100 * 1000 * 1000
等于 1 亿。它表示我们在一次探索性测试中选取的总点数。100 * 1000 * 1000
是一个乘法操作,将在编译期间运行之前执行。当前的乘法表达式在编码中不存在额外的计算时间。 -
for
-loop()
多次驱动相同的实验,即 10 次。
控制台上的输出如下所示:
3.14150492
3.14166436
3.14157872
3.14143904
3.14174756
3.14153872
3.14161072
3.14155196
3.14198448
3.14158056
该方法使用不到 10 行代码,完成执行并输出估计的 Pi 值大约需要 5 秒钟。
最后但同样重要的是,如果total
和i
的值太大,我们需要将int
改为long
。记住int
类型的数据最大可达 32 位,相当于 2 32 。当总数大于这个数时,我们将需要使用long
类型,它支持 64 位(等于 2 64 )。
public static double computePi(long total, int n) {
long count = 0;
for(long i=0; i < total; i++) {
double x = n * Math.random();
double y = n * Math.random();
if ((x - n/2) * (x - n/2) + (y - n/2) * (y - n/2)
< (n/2) * (n/2)) {
count++;
}
}
return (double)count * 4 / total;
}
参数的长值类型需要作为...L
传递,如下所示。然而,这将需要更长的时间来执行,除非它运行在一个高计算能力的 PC 上。
public static void main(String[] args) {
long hugeNumber = 1000 * 1000 * 1000 * 1000L;
for(int i=0; i < 10; i++) {
System.out.println(computePi(hugeNumber, 100));
}
}
如果我们增加total
的值,它将通过更多的点覆盖更多的区域,并返回更精确的圆周率结果。对于我们在这个程序中选择的 1 亿个点,它几乎保证找出 Pi = 3.141...,精确到千分之一。为了达到更高的精度,我们需要一台更强大的计算机。至少我们知道,有了理想的计算平台,我们将能够在指定的精度水平上确定圆周率值。
从这个例子中,我们了解到,只要我们知道曲线的函数模型,我们可以应用相同的比率和概率概念来找出由任何曲线包围的区域。
例子
牢记概率概念,用 Java 编程求直线 x = 0,y = 0,曲线 y = -2x 2 + 12x -18 之间的面积。
回答
这种方法已经帮助我们解决了一个问题,而一个纯数学的解决方案原本需要微积分的知识。
public static double computeArea(int total) {
int count = 0;
for(int i=0; i < total; i++) {
double x = Math.random() * 3;
double y = Math.random() * -18;
if (-2 * x * x + 12 * x - 18 < y) {
count++;
}
}
return (double)count * 54 / total;
}
问题
创建一个程序,找出欧拉数 e。
二十四、面向对象编程中的类
一个对象本质上是一个事物的表示。一个物体有一些属性,就像任何事物都有特性一样。然而,编程世界中的类主要是为特定对象设计的数据结构。这个类也被认为是一个对象的蓝图。它跟踪关于该对象的一系列相关事物。这些相关的东西被称为字段、属性和函数(或方法)。
字段是类的数据成员。在使用它们之前,必须声明和初始化它们。它们主要供班级内部使用。
一些字段可以充当属性,即对象的属性(例如,雇员的姓名或银行账户的余额)。
属性可以被 setters 改变,也可以被 getters 从类外部访问。
Getters 和 setters 是隐藏类属性内部实现的方法。这种设计使开发人员能够在以后轻松地更新一些现有的实现。它是面向对象编程的封装特性的一个例子。
在下面的示例类Student
中,
-
firstName
、lastName
、age
都是字段。因为所有这些字段都有 getter/setter,例如,getAge()
,setAge()
,所以它们也是属性。 -
public Student()
是在Student
类中定义的默认构造函数。它就像一个方法,当从Student
类创建对象时执行,即:Student student = new Student();
-
public Student(String firstName, String lastName)
是Student
类的另一个构造函数。它通过直接将firstName
和lastName
分配给字段来实例化(或创建)一个对象,即:Student student = new Student("John", "Doe");
-
public String getFirstName()
、public String getLastName()
、public int getAge()
是查询私有字段firstName
、lastName
、age
的值的方法。他们是吸气剂。 -
public void setAge(int age)
是给私有字段age
赋值的方法。它是二传手。 -
数据类型前面的关键字
public
和private
(如String
、int
)或方法名称为访问修饰符。public
意味着它对所有人都可见,而private
只对当前的类范围开放。public class Student { private String firstName; private String lastName; private int age; public Student() { } public Student(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age ; } }
一个简单的结构视图是:
class Student { //fields, e.g. firstName, lastName, and age //constructor //setter/getter methods, e.g. get/set firstName, lastName, and age }
Student
类中的关键字this
是对当前对象的引用,其字段(如firstName
、lastName
、age
)正在被使用。通过使用this
,也可以调用当前对象的方法或构造函数。
程序中有两种所谓的非字段。
-
方法中的局部变量
-
参数
x1
和x2
的方法如下:myMethod(x1, x2)
类和对象之间的关系:
-
不上课可以有对象吗?不
-
没有对象可以上课吗?是
-
我可以创建一个类的多个实例吗?是
实验室工作
创建一个名为Name
的类来表示一个人的名字。这个类应该有名为firstName
的字段代表这个人的名,lastName
代表他们的姓,middleInitial
代表他们的中间名首字母(单个字符)。目前,您的类应该只包含字段。
实验室工作
创建一个名为Vehicle
的公共类的大纲。
public class Vehicle {
......
}
然后创建一个主程序来操作创建的Vehicle
对象。
实验室工作
向下面显示的Point
类添加一个构造函数,它接受另一个Point
作为参数,并将新的Point
初始化为具有相同的(x,y)值。在您的解决方案中使用关键字this
。
然后,将名为setX
和setY
的方法添加到Point
类。每个方法都接受一个整数参数,并将Point
对象的 x 或 y 坐标分别更改为传递的值。
public class Point {
int x;
int y;
// your code goes here
}
问题
-
一个对象和一个类有什么不同?
-
对象用于面向对象的编程,类用于面向类的编程。
-
对象是封装相关数据和行为的实体,而类是一类对象的蓝图。
-
一个对象不封装,一个类封装,使得类比对象更强大,更可重用。
-
对象是一种不包含任何行为的类。
-
类是对象的实例。一个对象可以用来创建许多类。
-
-
描述一个关于类和对象的概念是如何被使用的真实场景。
-
设计并编写一个名为
Game
的类。你需要考虑这个类应该有什么样的字段,然后添加几个方法来丰富这个类。For example,
-
你可以定义游戏的价格;
-
你可以将游戏分为“电脑游戏”或“电子游戏”;
-
你可以定义游戏可以使用的平台,例如“xbox”、“playstation”、“nintendo”等。;
-
您可以用参数定义构造函数;
-
您可以定义方法来设置/获取上面提到的任何字段;
-
当你想把一个
Game
定义为一个类时,你能想到的任何东西。
-
-
给定下面这个名为
NumberHolder
的类,编写一些代码来创建该类的一个实例,初始化它的两个成员变量,然后显示每个成员变量的值。public class NumberHolder { public int anInt; public float aFloat; }
-
以下哪些是字段和参数之间的区别?这个问题可能有多个答案。
-
字段是存在于对象内部的变量,而参数是方法内部的变量,其值是从外部传入的。
-
字段可以存储许多值,而参数只能存储一个值。字段语法不同,因为它们可以用关键字
private
声明。 -
参数必须是值的基本类型,而字段可以是对象。
-
字段的范围是整个类,而参数的范围仅限于方法。
-
在计算机中,字段比参数占用更多的内存。
-
每个类只能有一个字段,但是可以有任意多个参数。
-
字段是常量,只能设置一次,而参数在每次调用时都会改变。
-
-
假设
Account
类中的方法被定义为:public double computeInterest(int rate)
And suppose the client code has declared an
Account
variable namedacct
. Which of the following would be a valid call to the above method?-
int result = Account.computeInterest(14);
-
double result = acct.computeInterest(14);
-
double result = computeInterest(acct, 14);
-
new Account(14).computeInterest();
-
acct.computeInterest(14, 15);
-
二十五、接口——整体抽象
接口的概念是抽象的一部分,是四个面向对象的概念之一。特点。抽象是关于公共特性的抽象设计,包括对象的操作。
接口是一个类的蓝图。但是,它既不是类,也不是对象。接口中定义的所有方法都是抽象的。接口的任何方法中都不允许有实现细节。将要实现接口的类将负责方法的实际实现。
让我们看一个接口和从它实现的类的例子。Auto
是代表车辆的通称。我们使用Auto
来定义一个接口。
public interface Auto {
void start();
void stop();
void turn();
void back();
void park();
}
作为汽车的两种常见类型,轿车和公共汽车是常见的对象。轿车和公共汽车共享在Auto
接口中定义的相同类型的行为。当我们为汽车和公共汽车创建一个类时,我们使用关键字implements
从相同的Auto
接口用不同的行为细节实现汽车和公共汽车。
我们使用类Car
作为例子:
public class Car implements Auto {
private String maker;
public void start() {
// car starts its engine
}
public void stop() {
// car stops its engine
}
public void turn() {
// car turns left or right at a corner
}
public void back() {
// car backs
}
public void park() {
// car parks
}
public String getMaker() {
return this.maker;
}
public void setMaker(String maker) {
this.maker = maker;
}
}
你可能已经注意到了,
-
一个接口指示对象能做什么。
-
当一个类实现接口时,它用必要的细节定义对象正在做什么。
接口的设计允许开发人员在不改变调用者实现的情况下修改底层类;这有时被称为接口编码。至少在两种情况下我们应该考虑采用接口设计。
-
当我们只想指定特定数据类型的行为,而不关心谁实现了它的行为时。
例如:
我们定义了一个接口
Auto
,它是一个抽象概念和通用术语。在这个接口中,我们通过签名定义了几个方法,如start
、stop
、turn
、back
、park
,没有任何实现细节。当我们创建实现Auto
接口的Car
或Truck
类时,我们将在这些方法中添加实现细节。 -
当所有的类都有相同的结构,但是它们有完全不同的功能时。
例如:
Dogs and cats communicate in totally different ways. A dog barks, but a cat meows. We may define an interface
Animal
and create a classDog
and classCat
, like shown here.public interface Animal { public void communicate(); } public class Dog implements Animal { public void communicate() { System.out.println("bark, bark!"); } } public class Cat implements Animal { public void communicate() { System.out.println("meow, meow..."); } }
Java 支持多接口实现:例如,如果我们定义了另一个接口,MovingObject
如图所示。
public interface MovingObject {
void movingNorth();
void movingSouth();
}
类Car
可以从两个接口实现,Auto
和MovingObject
,如下所示。
public class Car implements Auto, MovingObject {
...
public void movingNorth() {
// car moves North
}
public void movingSouth() {
// car moves South
}
}
二十六、继承——代码重用
作为 OOP 原则之一,继承旨在集中许多不同对象的公共功能。因此,它减少了许多类中的重复代码。
继承引入了两类:“超类”和“子类”子类继承自超类。超类和“基类”是一回事子类不仅包含从超类继承的所有方法和字段,还包含子类定义的其他方法和字段。
例如,我们定义了一个名为Sedan
的新类,它继承了我们之前创建的Car
类。Car
类实现了一个名为Auto
的接口。在Sedan
类中,我们定义了一个布尔字段isFourDoorHatchback
和一个名为isFourWheelDrive()
的方法。
关键字extends
用于描述Sedan
从类Car
继承的类。
public class Sedan extends Car {
public Boolean isFourDoorHatchback;
public Boolean isFourWheelDrive(){
return true;
}
}
然后我们在一个Driver
类中创建一个main
方法来使用Sedan
类。
public class Driver {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.start();
sedan.stop();
sedan.turn();
sedan.back();
sedan.park();
sedan.setMaker("Toyota");
sedan.getMaker();
sedan.isFourDoorHatchback = true;
sedan.isFourWheelDrive();
}
}
如您所见,Sedan
对象(即sedan
)拥有从其超类Car
继承的所有方法和字段。除此之外,Sedan
类有自己的方法和字段。用同样的main
方法,我们添加更多的代码:
Car car = new Sedan();
car.start();
car.stop();
car.turn();
car.back();
car.park();
car.setMaker("Toyota");
car.getMaker();
这个例子告诉我们,我们可以从一个超类(即Car
)创建一个对象,这个超类是从它的子类(即Sedan
)实例化而来的。它的超类下的所有方法和字段都像预期的那样可用,但是它的子类下的方法和字段不可访问。
如果我们尝试去做:
Sedan sedan2 = new Car();
我们将得到错误消息:
"Type mismatch: cannot convert from Car to Sedan".
这清楚地告诉我们,不允许我们创建一个子类对象(即Sedan
)从它的超类(即Car
)实例化。
然而,在 Java 中,它不支持多重继承。相反,它使用一个接口来实现多重继承在其他编程语言中试图实现的相同目标。
问题
-
下面哪个是正确的语法来表示类
A
是B
的子类?-
public class A : super B {
-
public class B extends A {
-
public class A extends B {
-
public A(super B) {
-
public A implements B {
-
-
考虑以下类别:
public class Vehicle {...} public class Car extends Vehicle {...} public class SUV extends Car {...}
以下哪些是合法陈述?
-
Car c = new Vehicle();
-
SUV s = new SUV();
-
SUV s = new Car();
-
Car c = new SUV();
-
Vehicle v = new Car();
-
Vehicle v = new SUV();
-
二十七、封装和多态
除了“抽象”和“继承”,OOP 中还有另外两个原则,“封装”和“多态”
包装
您可能听说过“信息隐藏”这个短语,意思是将一个对象的详细实现隐藏在更高的抽象层次之后。信息隐藏主要是出于安全考虑,而封装是出于复杂性考虑,将数据和类实现细节保留在类内。然而,封装将内部数据和方法结合起来,使其内部数据可以通过其公共方法从外部访问。并且该类具有私有实例变量,这些变量只能由同一类中的方法访问。这有助于管理频繁更新的代码。这被称为:“封装变化的东西”,这是最佳实践设计原则之一。
在前面章节创建的Student
类中,我们有以下私有字段和公共方法。
-
private int age;
只能从类内部访问 -
public void setAge(int age);
从外部可访问的 setter -
public int getAge();
从外部获取
这是一个简单的封装示例,说明了我们如何设置学生的年龄值以及如何访问年龄信息。
public class TestStudent {
public static void main(String[] args) {
Student student = new Student("John", "Doe");
/*
student.age = 20;
This line will give compiler error
age field can't be used directly as it is private
*/
student.setAge(20);
System.out.println("Student name: " + student.getFirstName() + " " + student.getLastName() + "; age: " + student.getAge());
}
}
抽象和封装是有区别的。抽象是通过使用接口隐藏复杂性(即实现细节),而封装是将代码(即实现)和数据(即变量值)包装在同一个类中。
多态性
“聚”的意思是很多。“Morph”表示形式或形状。“多态性”是一个对象用许多不同的形式呈现同一界面的能力。在 Java 编程设计中有很多这样的例子。
-
通过一个接口,我们可以创建多个类。每个类实现相同的方法,但细节不同。
-
在基本类设计中,我们可以用不同的输入参数创建多个构造函数。
-
类似地,我们可以在类设计中使用相同的方法名和不同的输入参数集。这也称为“重载方法”
-
在子类中,我们可以“覆盖一个方法”,这个方法最初是在它的超类中定义的。
问题
-
写一个名为
GeometricObject
的接口,声明两个抽象方法:getPerimeter()
和getArea().
-
用受保护的变量
radius
编写实现类Circle
,实现接口GeometricObject
。
二十八、数组——一种简单高效的数据结构
当我们需要存储和操作一堆相同类型的数据时,我们需要考虑使用正确的数据结构。假设我们想要处理表示相同类别的数据,例如您学校的学生姓名和年龄。数据将需要被分类、查询或搜索,并且容易访问。而且,我们有时可能需要更新或删除一些数据。
Java 提供了一种称为数组的简单数据结构来满足这些需求。数组提供大量存储空间来容纳我们的数据。存储空间的每个元素的标签称为“索引”它是一个从 0 开始的整数。存储在数组中的数据可以是整数、字符或其他类型的数据。
例如:
- 定义了一个总共有 7 个元素的整数数组
numbers
int[] numbers = new int[7]
- 定义了一个总共有 4 个元素的字符数组
letters
char[] letters = new char[4]
有不同的方法来分配或更新数组中的元素值。
- 如果您必须为每个元素分配不同的值,您需要声明数组及其大小,然后为每个元素分配值,如下所示:
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 3;
numbers[2] = 2;
numbers[3] = 4;
numbers[4] = 5;
或者:
- 如果数组元素中的值有明确的模式,可以按以下方式赋值:
int[] numbers = new int[ ] { 1, 3, 2, 4, 5 };
int[] numbers = new int[7];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = 2 * i + 1;
}
数组的属性numbers.length
存储数组numbers
的大小值。
我们可以在运行时根据输入定义数组的大小,如下所示:
int k = scan.nextInt();
int[] numbers = new int[k];
例子
下面哪个选项是声明和初始化一个 8 整数数组的正确语法?
-
int a[8];
-
[]int a = [8]int;
-
int[8] a = new int[8];
-
int[] a = new int[8];
-
int a[8] = new int[8];
回答
(d)
实验室工作
-
写一行代码声明并初始化一个名为
data
的整数数组变量,元素值为 7,-1,13,24,6。 -
编写代码,创建一个名为
odds
的数组,使用一个for
循环将-16 到 48 之间的所有奇数存储到该数组中。确保数组有恰好合适的容量来存储这些奇数。
问题
-
下面哪一个选项是用一列特定值初始化五个整数的数组的正确语法?
-
int a { 14, 88, 27, -3, 2019 };
-
int[] a = new { 14, 88, 27, -3, 2019 } [5];
-
int[5] a = { 14, 88, 27, -3, 2019 };
-
int[] a = { 14, 88, 27, -3, 2019 };
-
int[] a = new int[ ] { 14, 88, 27, -3, 2019 };
-
-
执行以下代码后,数组编号有哪些元素值?
int[] numbers = new int[8]; numbers[1] = 4; numbers[4] = 99; numbers[7] = 2; int x = numbers[1]; numbers[x] = 44; numbers[numbers[1]] = 11;
二十九、常见陷阱
在这一章中,我想分享几段揭示编码实践中常见问题的代码。使用这些例子来诊断根本原因将有助于提高您的理解。我建议先独立思考,再寻求答案。你可能会在最后一章找到一些提示。
实验室工作
-
这里有什么问题吗?
String aAsString; String bAsString; Scanner user_input = new Scanner(System.in); System.out.println("a="); aAsString = user_input.next(); a = Integer.valueOf(aAsString); System.out.println("b="); bAsString = user_input.next(); b = Integer.valueOf(bAsString);
-
这里有错误吗?
public class TestArray { public static void main(String[] args) { int[] myArray = new int[] { 11, 12, 13, 14, 15 }; System.out.printf("%d\n", myArray[5]); } }
-
理解下面这个函数试图做什么,并思考如何改进它。
public static int CountStrings(String[] stringsArray, String countMe) { int occurences = 0; if (stringsArray.length == 0) { return occurences; // or, return 0; } for (int i = 0; i < stringsArray.length; i ++) { if (stringsArray[i].toLowerCase().contains(countMe.toLowerCase())) { occurences ++; } } return occurences; }
-
发现缺陷:
public class Rectangle { public int width; public int height; public int getArea() { return width*height; } } public class SomethingIsWrong { public static void main(String[] args) { Rectangle myRect; myRect.width = 40; myRect.height = 50; System.out.println("myRect's area is " + myRect.area()); } }
-
发现缺陷:
Scanner newscanner = new Scanner(System.in); System.out.print("Please enter today's date (month day):"); int z = newscanner.nextInt(); int y = news scanner.netInt(); if (z > 12 || y > 31) { System.out.println("You have entered an invalid number."); return; } else if (y > 31 && z > 12) { System.out.println("Both numbers you have entered are invalid."); return; }
-
发现缺陷:
System.out.println("What month were you born in? (1-12)"); Scanner sc = new Scanner(System.in); String a = sc.nextLine(); Integer result = Integer.valueOf(a); int al = result.intValue();
-
发现缺陷:
if (numToTake >= 2 && numToTake< 3) { numToTake = 2; } else if (numToTake > 2) { System.out.println("The number you have entered is invalid."); }
三十、设计考虑
我们已经学习了一些关于 Java 中的类和对象的基本概念。现在让我们从类设计的角度来看几个例子。
实际案例 1
下面是一个Rectangle
类的设计。它希望计算矩形的面积、周长和对角线,给定其宽度和高度值作为输入参数。
public class Rectangle {
private int width;
private int height;
private int area;
private double diagonal;
private int perimeter;
public Rectangle (int width, int height) {
this.width = width;
this.height = height;
this.area = width*height;
this.diagonal = Math.sqrt(width * width + height * height);
this.perimeter = (width + height) * 2;
}
public int getArea() {
return this.area;
}
public double getDiagonal() {
return this.diagonal;
}
public int getPerimeter() {
return this.perimeter;
}
}
面积、参数和对角线的计算在Rectangle
构造函数中完成,每次初始化Rectangle
类的对象时都会执行。如果我们一直想得到矩形的面积、周长和对角线的值,这是可行的。但是,当我们有时只想查询矩形的面积、周长或对角线时,某些部分的计算会变得过多。更好的设计方法是“按需”实现,如下所示。
public class Rectangle {
private int width;
private int height;
public Rectangle (int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return this.width * this.height;
}
public double getDiagonal() {
return Math.sqrt(this.width * this.width
+ this.height * this.height);
}
public int getPerimeter() {
return (this.width + this.height) * 2;
}
}
实际案例 2
下面的例子是一个Game
类设计的实现。除了几个私有字段类型设计选择之外,它看起来不错。
-
商品的价格通常是一个小整数加上小数点右边的两位小数。由于浮点运算的不准确性,无论是
float
类型还是double
类型都无法准确表示这种用于货币计算的数字形式。建议用美分来表示美元价格,因此您只需要程序来处理整数计算。在某些情况下,用美元计算货币可能就足够了。 -
gameType
不应该被定义为一个true
/false
布尔值。它应该使用“String
数据类型。(或者,如果我们知道gameType
的固定名称列表,我们可以考虑使用枚举法。)public class Game { private int price; private boolean gameType; private String platform; public Game() { } public int getPrice() { return this.price; } public int setPrice(int price) { return this.price=price; } public boolean getGameType() { return this.gameType; } public boolean setGameType(boolean gameType) { return this.gameType=gameType; } public String getPlatform() { return this.platform; } public String setPlatform(String platform) { return this.platform=platform; } }
实际案例 3
我们如何测试我们在 Eclipse 中设计的一个类?
至少有两种简单的方法。假设你已经设计了一个名为MyClass
的类。它有一个公共整数数据字段- myNumber
,和一个使其整数值加倍的方法- doubleMe()
。
方法
原始类和测试代码都包含在一个 Java 文件中,如下所示:
public class MyClass {
// class design part of code
public int myNumber;
public MyClass() { }
public int doubleMe() {
return this.myNumber * 2;
}
// test part of code
public static void main(String arg[]) {
// declare and initialize an object
MyClass myObject = new MyClass();
myObject.myNumber = 2019;
int output = myObject.doubleMe();
// output the resulting data and validate it
System.out.println("My result is: " + output);
}
}
方法
以下两个类位于不同的 Java 文件中:
在MyClass.java
中:
public class MyClass {
public int myNumber;
public MyClass() {
}
public int doubleMe() {
return this.myNumber * 2;
}
}
在TestMyClass.java
中:
public class TestMyClass {
public static void main(String arg[]) {
MyClass myObject = new MyClass();
myObject.myNumber = 2019;
int output = myObject.doubleMe();
System.out.println("My result is: " + output);
}
}
实际案例 4
静态和非静态字段或方法之间有什么区别?我们什么时候使用静态字段和静态方法?
在前面描述的大多数代码示例中,我们使用了非静态字段和方法(也称为实例字段和实例方法)。实例字段和实例方法都属于被实例化的对象,这意味着它们直到对象被创建后才被激活。
但是,静态字段和静态方法属于类级别。它们可以由类名访问,而不是由从该类实例化的任何对象访问。存储在静态字段中并由静态方法计算的值在从同一个类创建的所有对象之间共享。
第一个也是我们最熟悉的静态方法是"main()
"方法,如果您还记得的话。它可以驻留在任何公共类中。这个方法是任何应用程序的唯一入口点。它必须与一个类相关联。换句话说,它不存在于任何对象实例中。
在Demo
类示例中,有一个静态字段counter
,用于跟踪运行时创建的对象数量。有一个非静态字段(即实例字段)- myNumber
与一个单独的对象实例相关联。非静态方法(即实例方法)- getNumber()
也属于被创建的对象。
public class Demo {
private static int counter;
public static int getCounter() {
return counter;
}
private int myNumber;
public int getNumber() {
return this.myNumber;
}
public Demo(int number) {
this.myNumber = number;
counter++;
System.out.println("I am no. " + counter + " object so far.");
}
}
接下来是一个测试类,演示静态字段(即counter
)和静态方法(即Demo.getCounter()
)是如何工作的,并与非静态字段(即myNumber
)和非静态方法(即getNumber()
)进行比较。
public class TestDemo {
public static void main(String[] args) {
Demo demo1 = new Demo(21);
System.out.println("demo1 myNumber: " + demo1.getNumber());
System.out.println("object counts: " + Demo.getCounter());
Demo demo2 = new Demo(57);
System.out.println("demo2 myNumber: " + demo2.getNumber());
System.out.println("object counts: " + Demo.getCounter());
Demo demo3 = new Demo(99);
System.out.println("demo3 myNumber: " +
demo3.getNumber());
System.out.println("object counts: " + Demo.getCounter());
}
}
控制台的输出是:
I am no. 1 object so far.
demo1's myNumber: 21
object counts: 1
I am no. 2 object so far.
demo2's myNumber: 57
object counts: 2
I am no. 3 object so far.
demo3's myNumber: 99
object counts: 3
三十一、IOU 计算
IOU 的意思是“交集大于并集”它被用作图像检测技术中的度量。此指标计算两个矩形之间的重叠面积与其联合面积的比率。为了简单起见,这两个矩形在同一个方向,正如你在图 31-1 中看到的 R1 和 R2。
图 31-1
两个矩形及其重叠
为了计算这个比率,我们需要找出它们的重叠区域 x。如果两个矩形的面积分别是 R1.area 和 R2.area,那么
- IOU = X/(R1 . area+R2 . area–X)
我们用x_min
、y_min
、x_max
和y_max
来定义矩形的位置。它的四个顶点可以用四个坐标来表示:(x_min
、y_min
)、(x_min
、y_max
)、(x_max
、y_max
)、(x_max
、y_min
),从左下顶点开始,顺时针方向。
我们先来看看什么情况下 R1 和 R2 不会有重叠区域,如图 31-2 所示。
图 31-2
两个彼此分开的矩形
它将在以下时间出现:
-
R1.x_max <= R2.x_min,(1)
-
或者 R1.x_min >= R2.x_max,(2)
-
或者 R1.y_max <= R2.y_min,(3)
-
或者 R1.y_min >= R2.y_max (4)
如果从(1)到(4)的条件之一有效,则重叠面积为 0。
接下来,我们注意到重叠区域实际上被四条线所包围,如图 31-3 所示。
图 31-3
两个矩形及其重叠区域
-
x =最大值(R1.x_min,R2.x_min),x =最小值(R1.x_max,R2.x_max)
-
y =最大值(R1.y_min,R2.y_min),y =最小值(R1.y_max,R2.y_max)
根据数学推理,我们可以得出如下所示的编码设计方案:
有两类,Rectangle
和IntersectionOverUnion
。
Rectangle
类为 x-y 坐标系上的矩形定义了一个数据模型。
public class Rectangle {
public float x_min;
public float x_max;
public float y_min;
public float y_max;
public Rectangle(float xmin, float ymin, float xmax, float ymax) {
if (xmin >= xmax || ymin >= ymax) {
throw new IllegalArgumentException("Not a valid rectangle!");
}
this.x_min = xmin;
this.y_min = ymin;
this.x_max = xmax;
this.y_max = ymax;
}
public float getWidth() {
return this.x_max - this.x_min;
}
public float getHeight() {
return this.y_max - this.y_min;
}
}
IntersectionOverUnion
类包含驱动执行的main()
方法。
public class IntersectionOverUnion {
public static void main(String[] args) {
// test case 1
Rectangle r1 = new Rectangle(3f, 2f, 5f, 7f);
Rectangle r2 = new Rectangle(4f, 1f, 6f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
// test case 2
r1 = new Rectangle(3f, 2f, 5f, 7f);
r2 = new Rectangle(1f, 1f, 6f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
// test case 3
r1 = new Rectangle(3f, 2f, 5f, 7f);
r2 = new Rectangle(6f, 1f, 7f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
}
public static float getIOU(Rectangle r1, Rectangle r2) {
float areaR1 = r1.getHeight() * r1.getWidth();
float areaR2 = r2.getHeight() * r2.getWidth();
float overlapArea = 0f;
if (r1.x_min >= r2.x_max || r1.x_max <= r2.x_min ||
r1.y_min >= r2.y_max || r1.y_max <= r2.y_min) {
return 0f;
}
overlapArea = computeOverlap(
Math.max(r1.x_min, r2.x_min),
Math.min(r1.x_max, r2.x_max),
Math.max(r1.y_min, r2.y_min),
Math.min(r1.y_max, r2.y_max));
System.out.println(overlapArea + " / (" + areaR1
+ " + " + areaR2 + " - " + overlapArea + ")");
return overlapArea / (areaR1 + areaR2 - overlapArea);
}
private static float computeOverlap(
float x1,
float x2,
float y1,
float y2) {
float w = x2 - x1;
if (w < 0) w = -w;
float h = y2 - y1;
if (h < 0) h = -h;
return w * h;
}
}
我们还没完。我们需要经常思考如何改进我们的类设计和优化代码。在Rectangle
类中,有getWidth()
和getHeight()
方法。如果我们给Rectangle
类添加一个叫做getArea()
的方法会怎么样?
Rectangle
类更新为:
public class Rectangle {
public float x_min;
public float x_max;
public float y_min;
public float y_max;
public Rectangle(float xmin, float ymin, float xmax, float ymax) {
if (xmin >= xmax || ymin >= ymax) {
throw new IllegalArgumentException("Not a valid rectangle!");
}
this.x_min = xmin;
this.y_min = ymin;
this.x_max = xmax;
this.y_max = ymax;
}
public float getWidth() {
return this.x_max - this.x_min;
}
public float getHeight() {
return this.y_max - this.y_min;
}
public float getArea() {
return this.getWidth() * this.getHeight();
}
}
剩下的代码看起来会像这样:
import java.lang.Math;
public class IntersectionOverUnion {
public static void main(String[] args) {
// test case 1
Rectangle r1 = new Rectangle(3f, 2f, 5f, 7f);
Rectangle r2 = new Rectangle(4f, 1f, 6f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
// test case 2
r1 = new Rectangle(3f, 2f, 5f, 7f);
r2 = new Rectangle(1f, 1f, 6f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
// test case 3
r1 = new Rectangle(3f, 2f, 5f, 7f);
r2 = new Rectangle(6f, 1f, 7f, 8f);
System.out.println("IOU=" + getIOU(r1, r2));
}
public static float getIOU(Rectangle r1, Rectangle r2) {
float areaR1 = r1.getArea();
float areaR2 = r2.getArea();
float overlapArea = 0f;
if (r1.x_min >= r2.x_max || r1.x_max <= r2.x_min ||
r1.y_min >= r2.y_max || r1.y_max <= r2.y_min) {
return 0f;
}
overlapArea = computeOverlap(
Math.max(r1.x_min, r2.x_min),
Math.min(r1.x_max, r2.x_max),
Math.max(r1.y_min, r2.y_min),
Math.min(r1.y_max, r2.y_max));
System.out.println(overlapArea + " / (" + areaR1
+ " + " + areaR2 + " - " + overlapArea + ")");
return overlapArea / (areaR1 + areaR2 - overlapArea);
}
private static float computeOverlap(
float x1,
float x2,
float y1,
float y2) {
float w = x2 - x1;
if (w < 0) w = -w;
float h = y2 - y1;
if (h < 0) h = -h;
return w * h;
}
}
面积的计算现在封装在Rectangle
类中。这个变化本身并不大,但是我们应该习惯于在我们仍然能够逐步改进我们的程序设计的时候做一些小的改变。
三十二、项目
我想给你推荐一个动手项目清单,让你独立练习。完成这些项目肯定会帮助您加深对本书中描述的基本 Java 编程概念的理解。
项目甲
第一步
编写一个名为Rectangle
的类,表示一个矩形的二维区域。构造函数创建一个新的矩形,其左上角由给定的坐标指定,并具有给定的宽度和高度。
public Rectangle(int x, int y, int width, int height)
您的Rectangle
对象应该有以下方法:
-
public int getHeight()
-返回这个矩形的高度。 -
public int getWidth()
-返回这个矩形的宽度。 -
public int getX()
-返回这个矩形的 x 坐标。 -
public int getY()
-返回这个矩形的 y 坐标。 -
public String toString()
-返回该矩形的字符串表示,例如:
"Rectangle[x=1,y=2,width=3,height=4]"
第二步
将前面练习中的以下存取方法添加到您的Rectangle
类中:
public boolean contains(int x, int y)
public boolean contains(Point p)
Point
类的定义如下所示:
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
两个contains()
方法返回给定的Point
或坐标是否在这个Rectangle
的边界内的布尔状态。例如,一个[x=2,y=5,width=8,height=10]的矩形对于(2,5)到(10,15)之间的任何一点都将返回 true,这意味着包括了边。
项目
给定四个输入值,设计一个程序来找出当前日期和用户生日之间的天数。
程序提示输入用户的生日。提示会列出可供选择的值范围。请注意,打印的天数范围基于用户键入的月份中的天数。该程序打印生日的绝对日期。1 月 1 日是绝对日#1,12 月 31 日是绝对日#365。最后,程序打印离用户下一个生日还有多少天。如果生日是今天或明天,会出现不同的信息。下面是程序的四次运行及其预期输出(用户输入数据就在“?”后面)马克):
Please enter your birthday:
What is the month (1-12)? 11
What is the day (1-30)? 6
11/6 is day #310 of 365.
你的下一个生日在 105 天后,从今天算起。
项目 C
游戏规则是这样的:你从 21 根棍子开始,两个玩家轮流拿一根或两根棍子。拿最后一棒的玩家输了。你能设计一个程序来模拟游戏中两个玩家中的一个吗?一个玩家是用户,另一个玩家是电脑。
项目 D
编写一个名为hasVowel()
的方法,返回一个字符串是否包含任何元音(一个包含 a、e、I、o 或 u 的单字母字符串,不区分大小写)。
项目 E
编写一个名为gcd()
的方法,该方法接受两个整数作为参数,并返回这两个数字的最大公约数(GCD)。两个整数 a 和 b 的 GCD 是同时是 a 和 b 的因子的最大整数,任意数和 1 的 GCD 是 1,任意数和 0 的 GCD 是数。
计算两个数字的 GCD 的一个有效方法是使用欧几里德算法,该算法表述如下:
GCD(A,B) = GCD(B,A % B)
GCD(A,0)= A 的绝对值
例如:
-
gcd(24, 84)
返回 12 -
gcd(105, 45)
返回 15 -
gcd(0, 8)
退货 8
项目
编写一个名为toBinary()
的方法,该方法接受一个整数作为参数,并以二进制形式返回该数字的字符串表示。比如toBinary(42)
的调用应该会返回“101010”。
项目
使用下列卡片上的四个数字创建一个等于 24 的数学表达式。每张卡只能使用一次。把 ace 当成数字“1”。您可以在数学表达式中使用+
、-
、*
、/
、(
和)
。请找出所有可能的答案。
三十三、Java 进阶解决方案
作为参考,在这一章中,我将为你提供前几章中一些问题的答案提示。比如“为了 16。”意思是“第十六章中问题的提示”
为了 16。毕达哥拉斯三元组
-
不使用“c”,我们可以检查(a 2 + b 2 )是否是一个完美的平方数,取它的平方根并验证它是否是一个整数值。
-
使用示例代码并检查结果值(a 2 + b 2 )是否与“4n + 1”的形式相匹配。
为了 17。强类型编程
public boolean isCollinear(Point p) {
if (p.getX() == p1.getX() && p1.getX() == p2.getX()) {
return true;
}
if (this.getSlope(p) == this.getSlope()) {
return true;
}
return false;
}
public double getSlope(Point p) {
if (this.p1.x == this.p.x) {
throw new
IllegalStateException("Denominator cannot be 0");
}
return (double)(this.p.y - this.p1.y) / (this.p.x - this.p1.x);
}
18 岁。条件语句
-
它被重写,如下所示。
if (num < 10 && num > 0) { System.out.println("It's a one-digit number"); } else if (num < 100) { System.out.println("It's a two-digit number"); } else if (num < 1000) { System.out.println("It's a three-digit number"); } else if (num < 10000) { System.out.println("It's a four-digit number"); } else { System.out.println("The number is not between 1 & 9999"); }
-
这里显示了一个简化的版本。
if (a == 0) { if (b == 0) {...} else {...} } else { if (b != 0) {...} }
为了 19。switch
语句
switch(color) {
case 'R':
System.out.println("The color is red");
break;
case 'G':
System.out.println("The color is green");
break;
case 'B':
System.out.println("The color is black");
break;
case 'C':
default:
System.out.println("Some other color");
break;
}
21 岁。计算
-
定义 x 为孩子的数量,(2200–x)为成年人的数量,那么 1.5∫x+4∫(2200–x)= 5050。迭代 x = 0 到 2200,找到 x 的一个解。很明显,不存在一个以上的解。
-
将 x 定义为正确答案的数量,将(10–x)定义为错误答案的数量,则 5∫x–2(10–x)= 29。从 0 到 10 迭代 x,找到 x 的可能解。
-
迭代一个从 0 到 2001 的正整数,并检查它与 3、4 和 5 的整除性。
-
迭代每个三位数的整数,从 100 到 999,并检查其位数。
-
用一个递归的方法(参考例子)从三类植物(定义三类为 A、B、C)中重复挑选一个植物五次。然后从组合中删除重复项。例如:{A,A,B,B,C}是{A,B,A,B,C}的重复。
为了 23。Pi 的探索性实验
利用以下带有整数“r”的公式,近似计算“e”的值
为了 24。面向对象编程中的类
-
a)
-
b)
-
NumberHolder nh = new NumberHolder(); Nh.anInt = 5; Nh.aFloat = 3.2; System.out.printIn("anInt=" + Nh.anInt + "; aFloat=" + Nh.aFloat);
-
(一)、(四)
-
(二)
26 岁。继承——代码重用
-
(c)
-
(b)、(d)、(e)、(f)
为了 27。封装和多态
-
public interface GeometricObject { public abstract double getPerimeter(); public abstract double getArea(); }
-
public class Circle implements GeometricObject { private final double PI = 3.14159; protected double radius; public Circle(double radius) { this.radius = radius; } // Implement methods defined in the interface @Override public double getPerimeter() { return 2 * PI * this.radius; } @Override public double getArea() { return PI * this.radius * this.radius; } }
28 年。数组——一种简单高效的数据结构
-
(d)
29 元。常见陷阱
-
如果想得到一个整数值,为什么不一开始就取一个整数输入呢?
This is a corrected version. It is significantly simplified.
Scanner user_input = new Scanner(System.in); System.out.println("a="); int a = user_input.nextInt(); System.out.println("b="); int b = user_input.nextInt();
-
myArray[3]
等于“13”吗?注意数组元素索引的定义。
-
有必要查
stringsArray.length = 0
吗?在for
循环中做countMe.toLowerCase()
是一个好方法吗?This is a recommended version:
public static int CountStrings(String[] stringsArray, String countMe) { int occurences = 0; String keyword = countMe.toLowerCase(); for (int i = 0; i < stringsArray.length; i ++) { if (stringsArray[i].toLowerCase().contains(keyword)) { occurences ++; } } return occurences; }
-
myRect
初始化过吗?There is an important line to update in the
main()
method as shown here:public class SomethingIsWrong { public static void main(String[] args) { Rectangle myRect = new Rectangle(); myRect.width = 40; myRect.height = 50; System.out.println("myRect's area is " + myRect.area()); } }
-
既然变量
temp
已经被赋予了array1
中第一个元素的值,我们需要从for
循环中的i=0
开始迭代吗?The simple fix is to change from
for (int i = 0; ... to for (int = 1; ...
in the original function as shown.public static int getMaxLength(ArrayList<String> array1) { if(array1.isEmpty()) { return 0; } else { String temp= array1.get(0); for (int i = 1; i < array1.size(); i++) { if (array1.get(i).length() > temp.length() ) { temp= array1.get(i); } } return temp.length(); } }
-
检查
if
/else
子句。“
y > 31 && z > 12
的范围已经被“z > 12 || y > 31
的范围覆盖。因此,原代码中的“else if (...)
部分毫无意义。 -
查看
Scanner
的实际使用情况。Due to the same reason stated in 1, the code can be corrected as shown:
System.out.println("What month were you born in? (1-12)"); Scanner sc = new Scanner(System.in); int al = sc.nextInt();
-
检查
if
/else
子句numToTake > 2
的范围已经包含了numToTake >= 2 && numToTake < 3
的范围。if
和else if
条件句需要重写。
第一部分:Java 基础
第二部分:Java 进阶
Java Intermediate
读者在阅读本部分之前应该已经完成了第一部分:Java Basic。第二部分重点介绍我们如何学习 Java 编程,并整合基本的数学概念。
同样在第二部分中,我们通过许多实际例子演示了如何将 Java 编程应用于数学问题的解决。
读者将有机会见证 Java 编程如何在我们的实验工作中成为一个强大的工具。
我相信你会很高兴在这部分找到许多有趣的应用例子。虽然这本书没有触及每一个细节,但它将涵盖类和面向对象编程的基本概念,以便初学者能够建立一个良好的基础。