MengBridgeCourse 中文版

第 4 章 更多列表、循环、数组与分组

| 关于   英文版   «  3.4. 单元 4 实验 2 占位符   ::   目录   ::   4.2. 单元 4 实验 1 占位符  »

4.1. 聚合、字符串与更多循环

4.1.1. 面向对象设计:聚合、组合与委托

随着我们构建更复杂的程序,需要多个对象来建模问题。我们的对象需要以受控的方式协同工作。当涉及对象协同工作时,面向对象设计中使用了三个关键术语:

  • 聚合

  • 组合

  • 委托

一个对象可以拥有指向其他对象的字段,因此能够与它们进行通信。例如,一个岛屿知道其上所有的跳蛙(以及花朵、网)的情况。同样,表示一门课程的对象可能拥有字段来跟踪授课教师和学生。

聚合:当一个对象通过其字段“知晓”另一个或多个对象,从而能够与这些对象通信时,我们称这种关系为“聚合”。被关联的其他对象可能本身就有意义或有用,并且可以在第一个对象外部被访问。通常,这些其他对象会在第一个对象的构造函数中传入,或通过设置方法传入。

组合:另一种方式是,一个对象可以拥有指向其他对象的字段,这些对象代表其组成部分,我们称之为“组合”。例如,一辆汽车包含一台发动机、一个变速器和一个排气系统。类似地,一台自动售货机包含一个控制面板、一个商品存放系统、一个商品分发装置以及一个找零装置。

在组合中,主对象 包含 或 拥有 其组成部分。这些组成部分通常在构造函数内使用 new 创建。它们通常不会从对象外部提供。我们把较大的对象看作是以某种方式由这些组成部分 构建而成 的。

对于聚合和组合,主对象可以要求其关联的对象执行操作。这可以通过主对象调用它所知晓的对象的方法来实现。有时,主对象中的一个方法只是通过调用较低层级对象中的一个方法而“传递”下去。这被称为 委托:通过调用另一个对象的方法,请求其完成工作(或部分工作)。

4.1.2. 字符串与字符

4.1.2.1. 字符

到目前为止,在本课程中我们已学习了如何存储和操作各种类型的数据。我们使用 int 和 double 来表示数字,使用 boolean 来存储 true 和 false 值。字符是一种新类型,可用于存储单个字母、标点符号、符号、空格、制表符等。

要创建一个字符变量,我们需要像这样声明并实例化一个变量:

char letterA = 'a';
  • 使用 Java 类型 char 声明一个名为 letterA 的变量,该变量可存储单个字符,并将变量初始化为小写字母 "a"。注意,我们使用单引号(')将要作为程序文本一部分的字面字符值括起来。一些程序员将类型 char 读作与单词 "care" 相同,即 "character" 的第一个音节;另一些人则将其读作与单词 "char" 相同,例如 "char-broiled",因为这是其拼写方式。你会听到这两种发音,两者均可接受(且存在 even more)。

字符字面量,如 'a',用单引号括起来。与用双引号括起来的字符串字面量不同,字符字面量只能包含一个字符。

字符的使用方式与其他我们迄今为止所见的数据类型相同。可以使用关系运算符对它们进行比较:

if (letter == 'a')
{

}

自增和自减运算符可以作用于字符。因此,以下代码将正常工作:

char letter = 'a';
letter = letter + 1;  // now holds the value 'b'

Java 使用 Unicode 来表示字符,因此字符串可以存储西里尔字母和希腊字母等其他字母表的文本,以及中文等非字母语言的文本。你可以 read more about it。

在 Unicode 中,每个字符都由一个“码位”表示,你可以将其视为(有时也可当作)一个整数。

4.1.2.2. 字符串

用引号括起来的短语称为 字符串,因为它们包含一系列“字符”串联而成。字符可以是字母、数字、标点符号、符号、空格、制表符等。 String 用于存储文本,可视为一串字符。 String 还提供用于操作字符数据的方法。虽然 Java 中的单个字符是基本数据值,但 String 实际上是类的名称,而 String 实际上是对象。

假设我们想要创建一个 String 变量。可以这样创建:

String food = "banana";

此示例将创建一个名为 food 的 String 变量,该变量指向单词 "banana"。

我们也可以通过以下方式创建一个空字符串(其中不包含任何字符):

String food = "";

此示例将使用代表空字符串的对象来初始化变量。空字符串的字面值为""——即一对不包含任何字符的双引号。

4.1.2.3. 访问字符串中的单个字符

字符串提供了一个名为 charAt() 的方法,用于提取单个字符。它返回一个 char (而非多个字符的字符串)。

String fruit = "banana";
char letter = fruit.charAt(0); // stores just the letter b

0 作为参数表示我们想要获取位置 0 的字符。与上周的 Pixel 和 Picture 一样,字符串中的第一个字符存储在位置(或索引)0,而不是 1。在 Java 中,所有项目序列均从位置 0 开始。

4.1.2.4. 字符串是不可变的

字符串提供方法 toUpperCase() 和 toLowerCase() ,可将字符串中的所有字母转换为指定的大写或小写形式。这些方法常引发混淆,因为听起来像是会修改字符串本身。但无论是这些方法还是其他任何方法,都无法改变字符串,因为字符串是 不可变 的——这意味着对象的值无法被更改。实际上,所有字符串都是只读对象,字符串上的方法在返回结果时都会生成新的 String 对象,而不会修改原始字符串。

当您对字符串调用 toUpperCase() 时,会返回一个新的 String 对象。例如:

String name = "Ada Lovelace";
String nameUpper = name.toUpperCase();

这些语句执行后,upperName 指向字符串 "ADA LOVELACE"。但 name 仍然指向 "Ada Lovelace"。这两个变量分别指向两个完全独立的对象。

一种解决方法(如果我们愿意的话)是改变 name 变量所指向的内容。例如:

String name = "Ada Lovelace";
name = name.toUpperCase();

明确地说,我们无法通过运行 toUpperCase() 来改变字符串的值,此时仍然存在两个独立的对象。但我们可以改变变量所引用的内容,将其重新指向原始文本的第二个大写版本。通过这样做,我们就无法再引用原始的大小写混合 String 对象了,但如果不再需要它,就可以将该名称重新指向新对象。

另一个有用的 String 方法是 replace() ,用于查找并替换另一个字符串中的某一段字符串实例。

String text = "Computer Science is fun!";
text = text.replace("Computer Science", "CS");

本例演示了处理 String 方法的一种常见方式。它调用 text.replace() ,该方法返回一个指向新字符串的引用,“CS is fun!”。随后将该新字符串赋值给变量 text ,从而更新变量以指向新对象。

此赋值很重要;如果你不保存返回值,调用 text.replace() 将看似没有效果,因为变量 text 仍指向原始(未更改)的对象。

4.1.2.5. 字符串的查看

Jeroo 和 Pixel 是一种非常实用的教学方式,用于讲解对象的概念以及它们如何通过运行方法而改变。与这些对象不同,我们无法通过图形化方式立即观察 String 对象的内容。相反,若要查看变量所代表的值,我们必须编写一些代码。我们可以使用所谓的打印语句:

System.out.print("Hello World");

当此代码在 Eclipse 中运行时,会产生文本输出。Eclipse 会在主窗口底部的 Console 选项卡中显示你的代码所产生的文本输出。你应该看到该选项卡显示文本“Hello World”,不包含引号。值得注意的是,如果你运行以下内容,可能得不到你预期的结果:

System.out.print("Hello");
System.out.print("World");

这将显示“HelloWorld”在一行上,中间没有空格。通常,如果不同的字符串分行显示,输出会更容易阅读。为此,我们会将打印语句从 print 稍作修改为 println 。

System.out.println("Hello");
System.out.println("World");

这将导致“Hello”和“World”分别在不同的行上输出。

System.out.println() 追加一个特殊字符,称为 换行符,用于标记一行的结束,并将后续输出移动到下一行的开头。如果你不希望末尾出现换行符,或者希望将多个信息片段合并在同一行显示,可以使用 print() 代替 println() 。

4.1.2.6. 字符串上的运算符

一般来说,你不能对字符串执行数学运算,即使这些字符串看起来像数字。以下表达式是非法的:

// None of these work!
"Hello" - 1;
"World" / 3;
"Hello" * "World";

注意,有时你会看到看起来像数字的字符串。但只要看到引号,Java 就会将引号内的数据当作字符串处理,因此以下代码同样无法正常运行:

// these won't work either
"123" - 1;
"562" / 4;
"99" * "2";

然而, + 运算符适用于字符串,但其行为可能与你的预期不符。对于字符串, + 运算符执行 连接 操作,即把两个字符串首尾相连。因此 "Hello, " + "World!" 产生字符串 "Hello, World!" 。

这同样适用于变量:

String firstName = "Katie";
String lastName = "Brian";
String space = " ";

// create a string that will store the value "Katie Brian"
String firstAndLast = firstName + space + lastName;

由于 + 对数字和字符串均有效,Java 会执行你可能未预料到的自动转换:

System.out.println(1 + 2 + "Hello");
// produces: 3Hello

System.out.println("Hello" + 1 + 2);
// produces: Hello12

Java 从左到右执行这些操作。在第一行中, 1 + 2 是 3 ,而 3 + "Hello" 是 "3Hello" 。但在第二行中, "Hello" + 1 是 "Hello1" ,而 "Hello1" + 2 是 "Hello12" 。

连接操作在许多情况下都非常有用。例如,如果你在双引号内有一个很长的字符序列,请注意字符串字面量在程序中不能跨行:

// Will not compile!
String long = "This is a really
long line.";

相反,字符串字面量周围的双引号必须出现在同一行。然而,你仍然可以编写长字符串。只需使用连接操作即可:

String long = "This is a really "
    + "long line.";

如果字符串过长,可将其拆分为多个部分,每部分单独成行书写,并使用 + 将它们组合起来。

4.1.2.7. 常用的字符串方法

这里是一些你可能觉得有用的常见字符串方法。

MethodDescriptionExample
int length() Get the number of characters in the string.
int len = str.length();
boolean equals(Object other) Check to see if two strings have the same contents. Do NOT use ==, use equals().
if (str1.equals(str2))
{
    ...
}
boolean startsWith(String prefix) Check to see if one string begins with another (i.e., the other is a prefix of this string).
if (str1.startsWith(str2))
{
    ...
}
boolean endsWith(String suffix) Check to see if one string ends with another (i.e., the other is a suffix of this string).
if (str1.endsWith(str2))
{
    ...
}
String substring(int start, int end) Produce a new string consisting of a subsequence of characters, starting at position start and continuing up to but not including position end.
String sub = str.substring(3, 5);
String substring(int start) Produce a new string consisting of a subsequence of characters, starting at position start and continuing through all remaining characters (just as if you provided length() as the ending position).
String sub = str.substring(3);
int indexOf(char target) Search for the specified character starting from the beginning of the string, and return the position of the first occurrence, or -1 if it is not found.
int pos = str.indexOf('a');
String toLowerCase() Generate a new string containing the same content as this string, but with all characters replaced with their lowercase versions.
String lower = str.toLowerCase();
String toUpperCase() Generate a new string containing the same content as this string, but with all characters replaced with their uppercase versions.
String upper = str.toUpperCase();

4.1.2.8. 子串

一个 子串 是字符串中的一段连续字符序列——一种复杂说法,即字符串的一部分。 substring() 方法返回一个新字符串,该字符串从指定索引位置开始复制原有字符串中的字符。

String fruit = "banana";
String sub1 = fruit.substring(0); // returns "banana"
String sub2 = fruit.substring(2); // returns "nana"
String sub3 = fruit.substring(6); // returns ""

第一个示例返回整个字符串的副本,因为它从位置 0 开始。第二个示例返回除前两个字符外的所有字符(它从位置 2 开始,即第三个字母)。如最后一个示例所示, substring() 在参数为字符串长度时返回空字符串。

有其他版本的 substring() ,其参数有所不同。如果以两个参数调用,它们将被视作起始索引和结束索引:

String fruit = "banana";
String part1 = fruit.substring(0, 3); // returns "ban"
String part2 = fruit.substring(2, 5); // returns "nan"

注意,结束索引所指示的字符不包含在内。 substring() 方法包含从第一个位置开始的所有字符,一直延续到但不包括最后一个位置。

以这种方式定义 substring() 可简化一些常见操作。例如,要选取从索引 i 开始、长度为 len 的子串,可以写作:

String result = fruit.substring(i, i + len);

4.1.3. 理解自测:字符串

4.1.4. 计数控制循环

到目前为止,我们已经使用过 while 循环和 for-each 循环。

当你知道需要在何时停止,但不知道需要多少次迭代才能达到结束状态时,while 循环非常合适:

Jeroo jer = new Jeroo();
while (!jer.seesWater(AHEAD))
{
    jer.hop();
}

一个岛屿可以是任意大小,但杰鲁仍会跳跃直到碰到水。

for-each 循环在您知道需要处理组中每个项目时非常出色,例如当您想要更改图片中每个像素的值时:

Picture image = new Picture("image.png");
for (Pixel pix: image.getPixels())
{
    pix.setRed(0);
}

但如果我们想要执行一系列操作特定次数呢?虽然这两种循环 可以 完成这项任务,但更适合这种情况的是另一种 for 循环。

for (int i = 0; i < 10; i++)
{
    System.out.println("Looping!");
}

上面的代码将简单地打印出字符串“Looping!” 10 次。

for 循环包含三个用分号分隔的括号内成分: 初始化 、 条件 和 更新步骤 。

  1. 初始化 在循环开始时仅执行一次。通常,它会引入一个用于控制循环的局部变量。该变量常被用作 计数器 ,在一组数值中循环。

  2. 条件 在每次循环时都会被检查。只要其值为 true ,循环就会继续重复;当其值变为 false 时,循环结束。该条件仅在循环顶部检查一次,每次重复时检查一次,与 while 循环相同。

  3. 每次迭代结束后,执行 更新步骤,将计数器变量移动到下一个值,然后返回第2步。

for 循环通常更容易阅读,因为它将所有与循环相关的语句放在循环的顶部。与仅提供条件空间的 while 循环不同,它还提供了用于初始化一个旨在控制循环的变量的空间,并在每次循环迭代时更新该变量为新值。

for 循环与 while 循环之间有一个区别:如果你在初始化部分声明一个变量,该变量仅在 for 循环的循环体内部有效。例如,以下代码将产生错误:

for (int i = 0; i < 10; i++)
{
    System.out.println("Looping!");
}
System.out.println(i);   // would cause an error

最后一行尝试显示变量 i 的值(仅出于演示目的,无其他原因),但不会生效。如果需要在循环外部使用循环变量,必须在循环外部声明该变量,例如:

int i = 0;
for (i = 0; i < 10; i++)
{
    System.out.println("Looping!");
}
System.out.println(i);   // would NOT cause an error

这些循环中的更新步骤使用了符号 i++ ,并采用一元 自增运算符 ++ 。该运算符是“将 1 加到此变量上”的简写形式,与 i = i + 1 等价。像 i = i + 1 这样的赋值语句在 for 循环中不常出现,因为 Java 提供了更简洁的方式来实现加 1 或减 1 操作。虽然 ++ 与 i = i + 1 效果相同,但 -- 是 自减运算符 ;它将变量减去 1,效果等同于 i = i - 1 。若要以非 1 的数值来增加或减少变量,可使用 += 和 -= 。例如,i += 2 将 2 加到变量 i 上。

这很重要,因为您最常会看到的 for 循环看起来像这样:

for (int i = 0; i < 10; i++)
{
    // body of loop
}

同样,此循环中的变量 i 初始值为 0,每次迭代后加 1,直到其值被递增为 10,此时条件 ( i < 10 ) 不再 true 。

如果我们改而希望反转这一过程,让一个从 10 开始并递减的循环:

for (int i = 10; i > 0; i--)
{
    System.out.println(i + "...");
}
System.out.println("Blast off!");

这个循环将打印出从 10 开始递减的数字,然后输出短语“点火起飞!”。

4.1.4.1. 遍历字符串

  • 你可以使用一个 for 循环来遍历字符串中的字符。以下循环遍历 fruit 中的字符,并将它们逐个显示在每行上:

for (int i = 0; i < fruit.length(); i++)
{
    char letter = fruit.charAt(i);
    System.out.println(letter);
}

字符串提供了一个名为 length() 的方法,该方法返回字符串中的字符数量。由于这是一个方法,因此必须使用空参数列表调用它, () 。

条件是 i < fruit.length() ,表示当 i 等于字符串长度时,条件为 false ,循环终止。

要找到字符串的最后一个字母,你可能会试图尝试如下做法:

String fruit = "banana";
int length = fruit.length();
char last = fruit.charAt(length); // wrong!

这段代码可以编译并运行,但调用 charAt() 方法会抛出 StringIndexOutOfBoundsException 。问题在于 "banana" 字符串中不存在索引为 6 的字符。由于索引从 0 开始,这 6 个字母的索引范围是 0 到 5。要获取最后一个字符,必须从长度中减去 1。

String fruit = "banana";
int length = fruit.length();
char last = fruit.charAt(length - 1); // correct!

这是一个需要牢记的重要事项,因为在使用 for 循环遍历字符串时,这通常是一个常见错误:

String fruit = "banana";
for (int i = 0; i <= fruit.length(); i++) // wrong!
{
    ...
}

我们需将 for 循环中的条件写为 i < fruit.length() 或 i <= fruit.length() - 1 的形式。在 Java 中,使用 < 编写的循环更为常见,且这种比较方式与从 0 开始的位置更自然地契合。

许多字符串遍历操作涉及读取一个字符串并创建另一个字符串。例如,要反转一个字符串,我们只需逐个添加一个字符:

public String reverse(String s)
{
    String result = "";
    for (int i = s.length() - 1; i >= 0; i--)
    {
        result = result + s.charAt(i);
    }
    return result;
 }

result 的初始值为 "" ,即 空字符串。循环以逆序遍历 s 中的字符。每次循环迭代都会创建一个长度增加一位的新字符串,并将其赋给 result 。当循环结束时, result 包含 s 中字符的逆序排列。因此, reverse("banana") 的结果是 "ananab" 。

4.1.5. 理解自测:计数控制循环

4.1.6. 随机数使用技巧

4.1.6.1. 生成随机数

有时你可能会发现,在某些程序中做出随机选择有助于使你的决策更加独立和多样化。Java 提供了一个名为 Random 的内置类,用于生成随机数,而我们的学生类库中包含该类的一个特殊版本,对初学者特别有帮助。要使用此类,请在文件顶部添加以下导入语句:

import student.util.Random;

Random 类提供了一个名为 generator() 的方法,用于获取一个表示随机数生成器的对象。在此,我们仅需处理生成随机整数的情况,而该生成器提供了一个非常有用的方法。可以这样使用:

Random generator = Random.generator();   // local variable to refer to the random number generator
int value = generator.nextInt(4);        // generate a random number from 0 - 3

生成器提供了一个名为 nextInt() 的方法,用于生成随机整数。该方法接受一个参数,即上限值。当您提供此上限值时, nextInt() 方法将生成一个从 0(包含)到(但不包含)上限值的数字。

因此,例如,若要生成 0 到 99 之间的数字,您将调用 nextInt(100) 。

假设你希望以 15% 的概率执行某个操作。你可以这样做:

Random generator = Random.generator();   // local variable to refer to the random number generator
int value = generator.nextInt(100);
if (value < 15)
{
    //code in here will happen 15% of the time
}

此处,对 nextInt() 的调用将生成一个 0 到 99 之间的数字(共 100 种可能的取值),若生成的数字位于 0 到 14 范围内(共 15 种可能的取值,即 15% 的概率),则 if 语句将执行其真分支。

4.1.6.2. 测试随机行为

随机行为非常适合基于概率的事件。但随机行为也会让软件测试变得困难。当你在代码中加入随机行为后想要对其进行测试时,你的测试用例会做什么?假设你希望某个演员在特定情况下有百分之五十的概率向左转,另外百分之五十的概率向右转。如果你编写一个测试用例,使该演员处于那种情境下,它可能会向左转……也可能会不转。你该如何为这种情况编写测试?

答案很简单: Random 类帮助了你。考虑以下代码序列,它生成三个小于 100 的随机数:

// using the same local variable "generator" from before
int x = generator.nextInt(100);
int y = generator.nextInt(100);
int z = generator.nextInt(100);

编写使用此代码的测试用例会很困难,因为无法控制 x、y 和 z 中最终得到的值。然而,在编写测试用例时,Random 类提供了一个特殊方法 setNextInts(),可用来控制生成的数字,以供测试使用。使用方式如下:

// In your test case, do this:
Random.setNextInts(40, 50, 60);

// In the code you are testing, this happens:
int x = generator.nextInt(100);
int y = generator.nextInt(100);
int z = generator.nextInt(100);

// You know x will get the value 40, while y is 50, and z is 60

因此,当你测试具有随机行为的代码时,只需在测试用例中指定随机数生成器应产生的数值序列,即可使其行为变得可预测。在测试用例之外,生成器将产生真正(伪)随机的序列;但在你的测试用例内部,这些数值将完全由你决定。

4.1.7. 理解自测:随机数

4.1.8. 方法重写

Java 类层次结构中最通用的类是 java.lang.Object 类。它是所有出现在 Java 程序中的类的超类。默认情况下,它是任何在其类定义中未显式指定其他超类的类的直接超类。

对象类中最常用的方法之一是 toString() 方法:

public class Object
{
    public String toString();
}

该 toString() 方法返回其对象的字符串表示形式,但这些表示形式并不总是有用。例如,如果我们运行:

Jeroo jerr = new Jeroo();
System.out.println(jerr.toString());

我们会看到类似 Jeroo@5f93274e 的结果(尽管如果你运行这段代码,可能会看到不同的数字和字母组合)。

该实验表明, toString() 的默认定义返回其对象的某种内部表示形式。看起来它返回的是对象类名与其内存地址的拼接结果。这在某些应用中可能有用。但对于大多数对象,我们希望覆盖默认定义,使 toString() 方法返回一个更适用于当前所处理对象的字符串。

实际上,每个对象始终都有一个 toString() 方法可供使用。在打印语句中使用 toString() 非常普遍,因此当你直接对对象调用 println() 时, toString() 将会被自动调用。

Jeroo anna = new Jeroo();
System.out.println(anna);  // calls toString() automatically

4.1.8.1. 重写 toString() 方法

对于本例,我们考虑一个表示自动取款机的类,该类记录机器的位置以及机器中的现金数额。

public class ATM
{
    private String location;
    private double moneyInside;

    public ATM(String where, double amount)
    {
        this.location = where;
        this.moneyInside = amount;
    }
}

然后让我们创建这个类的一个对象。这个自动取款机将位于银行内,内部有 4000.00 美元(USD)。

ATM bankATM = new ATM("bank", 4000.00);
System.out.println(bankATM);
// output: ATM@149f5b42

打印 bankATM 会自动调用 ATM 上的 toString() ,但在输出中我们看到的是 toString() 的默认行为,这对了解这台机器的特性并无太大帮助。

相反,我们来编写一个 toString() 方法,该方法将更有助于告诉我们此自动取款机的位置。

由于每个类都已内置一个 toString() 方法,因此我们需要 重写 该方法,并将其行为替换为我们自己的实现。要重写一个方法,只需在子类中定义一个签名相同的方法即可。如果您使用子类的实例调用 toString() ,则会使用子类版本的方法。通过这种方式,子类方法将覆盖父类版本的方法。

首先,让我们将上面看到的方法签名添加到 ATM 类中。

public class ATM
{
    private String location;
    private double moneyInside;

    public ATM(String where, double amount)
    {
        this.location = where;
        this.moneyInside = amount;
    }

    public String toString()
    {

    }
}

由于我们正在重写对象固有的 toString() 方法,因此该方法签名必须与上方定义的完全一致。接下来,我们将创建一个 String 变量,用于指示此自动取款机的位置。

public class ATM
{
    private String location;
    private double moneyInside;

    public ATM(String where, double amount)
    {
        this.location = where;
        this.moneyInside = amount;
    }

    public String toString()
    {
        String data = "This ATM is at the: " + this.location;
    }
}

然后,由于 toString() 方法的返回类型为字符串,我们需要返回该字符串。

public class ATM
{
    private String location;
    private double moneyInside;

    public ATM(String where, double amount)
    {
        this.location = where;
        this.moneyInside = amount;
    }

    public String toString()
    {
        String data = "This ATM is at the: " + this.location;
        return data;
    }
}

现在让我们再次考虑我们的 bankATM …

ATM bankATM = new ATM("bank", 4000.00);
System.out.println(bankATM);
// output: "This ATM is at the: bank"

现在我们已经重写了 toString() 方法,就可以看到一些有用的信息了。

  • 虽然此新方法在 ATM 类中可能并不起重要作用,但它确实提供了对对象状态的非常简明且易于理解的描述。这正是为何 toString() 方法被包含在 Object 类中的原因。

4.1.9. 理解检查:方法重写

4.1.10. 语法练习 7a:for 循环与面向对象设计

4.1.11. 语法练习 7b:toString 与返回值

4.1.12. 编程练习 7a

4.1.13. 编程实践 7b

   «  3.4. 单元 4 实验 2 占位符   ::   目录   ::   4.2. 单元 4 实验 1 占位符  »

关闭窗口