Oh!Coder

Coding Life

有趣的Processing-数组进阶

| Comments

上一次,我们一起学习了Processing中数组的基本概念。比如,什么是数组,如何定义一个数组,以及数组当中,数据的读取和写入。这一次,我们将进一步学习数组,看看数组还有哪些我们需要进一步学习的内容。

嗯,这一节的学习,我们准备分为三部分,分别是数组方法,对象数组以及二维数组。好,接下来让我们逐一进行学习。

数组方法

Processing为了方便我们对数组数据的管理,提供了大量的数组辅助方法。限于篇幅,我们这里只举几个常见的例子,更多相关的方法需要去查看Processing的文档

首先,我们第一个要介绍的方法是append()。具体,我们还是结合例子进行学习。

打开Processing的IDE,新建一个项目窗口,如下图所示。

processing-new-window

图片来源:Processing新项目窗口

完成之后,将如下代码添入其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup(){
  // 定义字符串数组
  String[] trees = { "ash", "oak" };
  // 调用append()方法,
  // 将"maple"插入字符串数组trees的尾部
  // append()方法第一个参数为目标数组,
  // 第二个参数是要添加的数组元素
  trees = append(trees, "maple");
  // 打印字符串数组trees
  printArray(trees);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码
}

点击项目窗口中,菜单栏里的三角形按钮,编译并运行上述代码,如下图所示。

processing-window-menu

图片来源:Processing新项目窗口中的菜单栏

如果一切顺利,我们将在项目窗口下方的调试窗口,看到修改过后的数组trees。结果类似如下图所示。

processing-fun-array-more-1

图片来源:Processing程序执行结果

通过这个例子,我们学习了append()方法。这个方法实现的功能,是为指定的目标数组的末尾添加数据项。需要注意的一点是,append()方法返回一个新的数组,所以调用此方法的时候,需要在append()方法左侧给出一个相同类型的数组变量。例如,trees = append(trees, "maple")此处给出的仍然是数组trees,如果写成这样append(trees, "maple"),则无法获得添加”maple”后的数组。

接下来要介绍的一个方法,其功能与append()正好相反,方法名为shorten()append()方法向数组的末尾添加数据项,而shorten()则是删除数组末尾数据项。好,我们还是结合实际的例子进行学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup(){
  // 定义String类型数组trees
  String[] trees = { "lychee", "coconut", "fig" };
  // 调用shorten()方法,
  // 删除trees最后一个数据项"fig",
  // 返回一个新的数据,
  // 并重新赋值给trees。
  trees = shorten(trees);
  // 打印数组trees
  printArray(trees);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码
}

编译,并运行这段代码,可以在窗口下方的调试区域,看到输出结果大致如下图所示。

processing-fun-array-more-2

图片来源:Processing程序执行结果

shorten()方法的使用方式和append()类似,都是返回一个全新的数组,需要在方法调用的左侧给出一个相同类型的数组变量。同样的,如果只是像这样调用shorten(trees),不会有没有任何效果。

接下来我们要介绍的一个方法是expand()。看到方法名称,大概可以猜到,此方法是增加数组容量的。比如,一个数组在第一次定义的时候只能包含十个数据项,以后若想再往里存数据项,则需要先将数组容量扩大,expand()方法起的就是数组容量扩容的功能。那么,我们还是结合实际的例子来学习。看看这个方法,到底如何使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 定义数组x
int[] x = new int[100];
// 定义计数变量
// 此变量记录了当前x数组
// 共保存了多少个鼠标x轴坐标值
int count = 0;
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(100, 100);
}
// 画图。
// 此方法被系统默认循环调用。
void draw() {
  // 将鼠标x轴坐标值
  // 存入数组x中
  x[count] = mouseX;
  // 鼠标x轴坐标值计数+1
  count++;
  // 如果技术变量等于数组x的容量,
  // 此时说明数组x容量已满,
  // 需要对数组x进行扩容。
  // 扩容的范围是将当前容量翻倍。
  // 例如数组x初始容量为100,
  // 则第一次扩容后为200,第二次为400,
  // 以此类推。
  if (count == x.length) {
    // 对数组x进行扩容
    x = expand(x);
    // 每次扩容完毕,
    // 则打印当前数组x的容量值
    println(x.length);
  }
}

编译,并运行上述代码,如果一切顺利,在窗口下方调试区域,会看到类似于如下输出。

processing-fun-array-more-3

图片来源:Processing程序执行结果

这个方法的使用方式很简单,只有一个参数,即传入要扩容的数组变量,返回一个扩容好的数组。和上面两个方法一样,别忘了在此方法调用的左侧,给出同一类型的数组变量。同样,如果只是像这样的调用expand(x),其实是没有效果的。

好。接下来,我们再讲最后一个常见方法,更多的相关方法,可以在平时使用过程中,遇到之后再参考文档。

最后介绍的这个方法,功能是对数组的拷贝。我们知道,通常来说,如果将一个数组变量直接赋值给另一个变量,其实并没有达到数组数据拷贝的目的,只是做了简单的变量引用,数组中的数据,并没用因为赋值而变成双倍。当我们修改其中一个数组变量的数据时,会同时修改另一个数组的数据,为了避免这个问题,我们需要做数据拷贝的操作。所以,arrayCopy()方法的功能就是对数组数据进行拷贝。理论先说到这里,接下来让我们结合具体的例子,看看如何使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口的大小
  size(100, 100);
  // 定义String类型数组north
  String[] north = { "OH", "IN", "MI" };
  // 定义String类型数组south
  String[] south = { "GA", "FL", "NC" };
  // 将数组north内容拷贝至数组south
  arrayCopy(north, south);
  // 打印数组south内容
  printArray(south);
}
// 画图
// 此方法被系统默认循环调用
void draw() {
  // 此处无代码。开心不?
}

编译并运行上述代码,如果一切顺利,会在窗口下方的调试区内,看到如下打印结果。

processing-fun-array-more-4

图片来源:Processing程序执行结果

可以看到,arrayCopy()方法有两个参数,左边第一个,是要拷贝的源数组,第二个是要拷贝到的目标数组。按照上述例子来说,就是将north数组的内容拷贝到south数组中。与上面几个方法不同,此方法没有返回值,也就是说,此方法的左侧不需要有变量接收返回值。

上述,我们一口气介绍了四个常见的数组方法。关于数组的辅助方法,我们先介绍这些,以做示例。更多的相关方法,以后平时遇到了,可以参考Processing的参考文档

对象数组

在前几次的学习中,我们知道了如何在Processing中自定义类型,同时我们也学会了使用自定义的类型创建对象。数组的创建,除了可以创建基本类型的数组以外,还可以根据自定义类型创建对象数组。好!下面就让我们看看,如何根据自定义类型创建对象数组。具体让我们还是结合具体的例子学习。

首先,还是新建一个项目窗口。然后,将如下代码添入其中,我们结合代码来学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// 定义Ring类型数组,
// 数组变量名为rings
Ring[] rings;
// 定义rings数组容量为50 
int numRings = 50;
// 记录当前屏幕上已经显示的rings的个数
int currentRing = 0;
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设定窗口的大小
  size(100, 100);
  // 创建Ring类型数组
  rings = new Ring[numRings];
  // 为数组rings的每一个数据项创建对象
  for (int i = 0; i < rings.length; i++) {
    // 创建类型为Ring的对象
    rings[i] = new Ring();
  }
}
// 画图。
// 此方法被系统默认循环调用。
void draw() {
  // 设置窗口背景色为黑色
  background(0);
  // 循环调用数组rings中的对象方法
  for (int i = 0; i < rings.length; i++) {
    // 调用ring对象的grow()方法
    rings[i].grow();
    // 调用ring对象的display()方法
    rings[i].display();
  }
}
// 当鼠标按下时,
// 系统会自动调用此方法
void mousePressed() {
  // 设置ring对象的显示坐标点为鼠标按下位置
  rings[currentRing].start(mouseX, mouseY);
  // 每显示一个ring对象,
  // 即将currentRing变量加一,
  // 以此记录当前屏幕上共显示了多少个ring对象
  currentRing++;
  // 当currentRing数值超过rings数组的范围时,
  // 即将计数变量currentRing回零。
  // 以此避免越界访问rings数组。
  // 也就是说,此处rings数组最大数为50,
  // 若currentRing加到50,访问rings[50]将会出错。
  // 将currentRing回零的目的就是避免越界访问的错误。
  if (currentRing >= numRings) {
    // 将currentRing回零
    currentRing = 0;
  }
}
// 自定义Ring类型
class Ring {
  // 定义ring的x,y坐标点
  float x, y;
  // 定义ring的直径
  float diameter;
  // 控制ring直径是否变化
  boolean on = false;
  // 初始化ring对象数据
  void start(float xpos, float ypos) {
    // 初始化ring的坐标点x,y
    x = xpos;
    y = ypos;
    // 初始化diameter直径为1
    diameter = 1;
    // 设置ring的直径可变化
    on = true;
  }
  // 改变ring的直径
  void grow() {
    if (on == true) {
      // ring的直径变化幅度为每次0.5
      diameter += 0.5;
      // 如果直径大于400,
      // 则停止ring的直径增长
      if (diameter > 400) {
        // 停止ring继续变化
        on = false;
        // 重新将ring的直径设置为1
        diameter = 1;
      }
    }
  }
  // 显示ring
  void display() {
    // 若ring的直径可变化,
    // 则显示圆形
    if (on == true) {
      // ring没有填充色
      noFill();
      // 设置ring的轮廓宽度
      strokeWeight(4);
      // 设置ring的轮廓颜色
      stroke(204, 153);
      // 画ring。
      // 左边开始,前两个参数为x,y坐标
      // 后两个参数为椭圆的长直径和短直径,
      // 此处相等,故所画为圆形
      ellipse(x, y, diameter, diameter);
    }
  }
}

这次的代码有些长。编译并运行上述示例,如果一切顺利,可看到大致如下的运行结果。

processing-fun-array-more-5

图片来源:Processing程序执行结果

通过上述示例,我们可以看到,定义对象数组,其实和定义基本类型的数组没有太大区别,步骤都一样。有两点需要强调一下,一个是声明的时候,类型是自定义类型,如上例中是Ring[] rings,另一个是分配的时候,需要针对每一个数组项进行分配,如上例中是:

1
2
3
4
5
  // 为数组rings的每一个数据项创建对象
  for (int i = 0; i < rings.length; i++) {
    // 创建类型为Ring的对象
    rings[i] = new Ring();
  }

关于类型的定义,我们已经在之前学习过,这里不再赘述。

下面,我们学习今天最后一个话题,那就是二维数组。

二维数组

之前我们学过的数组都是一维数组。现在,我们来看看如何定义二维数组。二维数组的定义,本质上也要遵循一维数组的定义步骤,即声明,创建,分配。下面,我们通过具体的示例,来体会一下二维数组。

首先,还是新建一个项目窗口,将下面代码添入其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义二维数组x
int[][] x = { {50, 0}, {61,204}, {83,51}, {69,102},
{71, 0}, {50,153}, {29, 0}, {31,51},
{17,102}, {39,204} };
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  size(100, 100);
}
// 画图。
// 此方法被系统默认循环调用。
void draw() {
  // 利用二维数组x的数据,
  // 画出矩形
  for (int i = 0; i < x.length; i++) {
    fill(x[i][1]);
    rect(0, i*10, x[i][0], 8);
  }
}

编译并运行上述代码,运行结果大致如下图所示。

processing-fun-array-more-6

图片来源:Processing新项目窗口

现在,让我们仔细看看二维数组是如何定义的。这里对于二维数组的定义,显然是将声明、创建以及分配合并成了一步,定义的书写规则,在类型后面多出了一对[]符号,而且在一维数组的基础上,对每个数据项又套了一层{}符号,符号里的第二个数,是二维数组的数据。

processing-fun-array-more-7

图片来源:Processing官网

对照上图,可以更直观的理解二维数组。通过图来示意,可以更形象的帮助理解。

总结

这次关于数组,我们从三个方面进行了学习,分别是数组方法,对象数组以及二维数组。在数组方法的学习过程中,我们列举了四个常用的数组辅助方法。在对象数组部分,我们结合自定义类型,然后创建对象数组。最后的二维数组,我们在一维数组的基础上,又增加了一维数组,结合示例中给出的图,可以帮助大家理解。今天学习的内容看似较多,涉及到之前对于Processing自定义类型的知识。

下期预告

下一次,我们将学习图片相关的基础知识,其中包括图片的展示以及对像素的基本操作等。好!我们下次见!

Comments