版主
☯ 博 丽 不 是 灵 梦 ☯
- 经验
- 7085
- 硬币
- 1191 枚
|
楼主 |
发表于 2022-10-9 12:18:57
|
显示全部楼层
本帖最后由 电童·Isamo 于 2023-5-3 22:32 编辑
GDScript其六:数组 在前面的几节课的学习中,我们已经初步掌握了编写gdscript的基本操作。那么在本节课和下节课的学习中,我们将会学习GDScript中的两种容器数据类型——数组(Array)和字典(Dictionary)的用法。
本节课我们将开始学习GDScript中数组的用法。
在我们学习数组之前,我们先来看一个例子:
假设我有一系列数据,如下所示:
- extends Node
- var a1:int = 1
- var a2:int = 4
- var a3:int = 9
- var a4:int = 17
- var a5:int = 28
复制代码 那么,我们假设有一个方法foo,需要调用这一系列数据,那么就需要一个个地分行进行调用:
- func foo() -> void:
- a1 = 2
- a2 = 6
- a3 = 9
- a4 = 18
- a5 = 30
复制代码 但是这样一来有一个弊端:假如这一系列数据不是5个,而是20个、50个,甚至更多,甚至你连有多少个都不知道。这个时候,我们就需要用一个【容器】来将这些数据的值依次存入,并且我们要想引用或调用其中的值,我们也只需要通过这个容器来实现即可。这个容器就是数组(Array)
因此,数组就是用来存储一系列数值的容器(Container)
这就好像一行书架,每一本书就是该行书架上的一个内容物。
声明数组的方法不同于声明一般数值,它的声明方法如下:
- var my_array:Array = [value1,value2,value3,...,valueN]
复制代码 其中my_array就是你要声明的数组名,给数组赋值时,等号右侧的一系列数值需要放在中括号"[ ]"内,这个中括号就是用来定义这个数组的容纳范围的。凡是在该中括号内的数值,均为该数组的一部分,我们称这些数值为该数组的元素(Elements)。
有些时候,我们希望知道该数组的某个元素在该数组中的位置,也就是这个元素位于该数组的第几位,这个位置就叫做这个元素在这个数组中的索引(Index)。它是一个int类型的数,表示的这个元素在该数组中的位码。
在访问这个数组中的元素的时候,我们就需要利用这个元素在这个数组中的索引来访问这个元素。
访问的方法如下:
- extends Node
- var my_array:Array = [1,2,3,4]
- # 访问某个数组内的元素时,先引用或调用该数组的名称,
- # 然后在其名称后面加上中括号"[ ]",并在括号内输入你要调用的元素所在的索引,
- # 索引0表示数组最左侧的元素,即左中括号"["右侧紧邻的这个元素
- # 若索引为正整数,则表示第一个元素右侧的第几个元素
- # 若索引为负整数,则索引-1表示该数组最右侧的元素,即右中括号"]"左侧第一个元素,
- # 小于-1的索引表示最后一个元素左侧第|n|-1个元素,如-2表示最后一个元素左侧第|-2|-1 = 1个元素
- var q = my_array[0]
- # 引用my_array的第一个元素:1
- func _ready() -> void:
- my_array[1] = 3
- # 调用my_array的第二个元素:2,并修改该值为3,
- # 则现在的my_array为[1,3,3,4]
- var r = my_array[-1] - 3
- # 引用了my_array的最后一个元素4,并在赋值时减去3,故该局部变量r为1
复制代码 因此,按照正常人的思路,我们也就可以这样子调用这个新的数组copy:
- extends Node
- var my_array:Array = [1,2,3,4]
- var copy:Array = my_array # 引用了my_array
- func _ready() -> void:
- copy[3] = 5 # 调用了copy的第四个元素
- print(my_array) # 将原数组打印出来?
复制代码 按照正常的思路,最后打印到控制台上的结果肯定是[1,2,3,4]而不是[1,2,3,5],对吧?然而实际上,现实却是非常残酷的:
其实这是因为:GDScript中的数组在直接引用自另一个数组时,会进行相互引调绑定(Arrays-link)。也就是说,如果你在声明一个数组的时候直接将其值引用自另一个数组,那么这两个数组就形成了链接关系,对其中一个数组进行修改,就会导致与之相链接的其它数组都会发生对应的变化。
刚才的例子如果我换作
- extends Node
- var my_array:Array = [1,2,3,4]
- var copy:Array = my_array # 引用了my_array
- func _ready() -> void:
- my_array[3] = 5
- print(copy)
复制代码 结果是类似的。
那么该怎么样才能让copy这个数组既能引用自my_array,又不会跟my_array相绑定呢?这时候,我们就需要在引用my_array时复制(Copy)一份该数组,并将该复制出来的数组赋值给copy,才可以使copy既拥有my_array的值,又不会跟my_array相互影响。
要想执行该操作,就需要对my_array调用duplicate()方法:
- var copy:Array = my_array.duplicate() # 使用my_array的拷贝以避免与my_array互相绑定
复制代码 这样,上面的例子中两个数组直接就不会相互影响了
利用数组的拷贝来声明数组,我们就可以更加方便、安全地调用这个数组了
同普通数值一样,数组也有其对应的运算,然而,它的运算却又不同于普通数值的四则运算。
由于数组的运算数目较多,接下来就以代码片段的形式来呈现给大家:
- extends Node
- var my_array:Array = [1,2,3,4]
- # 数组的加算/数组的合并
- # 将两个数组用"+"连接,其结果就是两个数组的元素融并后的新数组
- # 注:两个数组中的相同元素,在加算后不会被融并为一个元素。
- var new:Array = my_array + [4,5,6]
- # new = [1,2,3,4,4,5,6]
- # 数组的比较
- # 将两个数组用"=="连接,若两个数组内含有的元素完全一致,则结果为true,
- # 否则结果为false
- var new_test:Array = [1,2,3,4]
- var new2:bool = (new == my_array) # 结果为false
- var new3:bool = (new_test == my_array) # 结果为true
- # 数组的反置
- # 对目标数组调用invert()方法,使该数组反向排序
- var invert:Array = my_array.duplicate()
- invert.invert()
- # invert = [4,3,2,1]
- # 数组的乱置
- # 对目标数组调用shuffle()方法,将该数组内的元素随机重排
- var shuffle:Array = my_array.duplicate()
- shuffle.shuffle()
- # shuffle = [4,3,1,2]或者[1,2,4,3]或者[4,2,1,3]……
- # 数组的减算/数组元素的剔除
- # 对目标数组调用remove()方法或者erase()方法
- # remove(index:int),将该数组的index索引上的元素剔除出该数组
- # erase(value),将该数组中值为value的元素剔除出该数组
- var remove:Array = my_array.duplicate()
- remove.remove(2) # 将该数组中的第三个元素3剔除,结果为[1,2,4]
- remove.erase(4) # 将该数组中值为4的元素剔除,结果为[1,2,3]
- # 数组的扩算
- # 对目标数组调用append()/push_back()方法、insert()方法或push_front()方法
- # append(value)/push_back(value),向该数组的末尾追加一个值为value的元素
- # insert(index:int,value),在索引所指向的元素后面插入一个值为value的元素
- # push_front(value),在该数组的第一个元素前面插入一个值为value的元素
- var front:Array = my_array.duplicate()
- front.push_front(0) # 在该数组的第一个元素1前插入元素0,则结果为[0,1,2,3,4]
- var append:Array = my_array.duplicate()
- append.append(10) # 在该数组末尾追加一个元素10,则结果为[1,2,3,4,10]
- var insert:Array = my_array.duplicate()
- insert.insert(2,6) # 在该数组的第三个元素3后面插入一个元素6,则结果为[1,2,3,6,4]
- # 数组的放缩
- # 对目标数组调用resize()方法
- # resize(size:int),以第一个元素为准,将该数组向右进行扩展或从右向左进行收缩
- # 若扩展出了原数组中没有的新元素,则这些新元素的值为null
- # 若从右向左收缩了该数组,则会从右向左依次剔除这些超出收缩后的数组范围的元素
- # 对于扩展数组,可以使用fill()方法对其默认值进行调整。
- var zoom:Array = my_array.duplicate()
- zoom.resize(7) # zoom = [1,2,3,4,null,null,null]
- zoom.fill(1) # zoom = [1,2,3,4,1,1,1]
- var zoom2:Array = my_array.duplicate()
- zoom2.resize(2) # zoom2 = [1,2]
- # 数组的清空
- # 对目标数组调用clear()方法,将该数组内所有元素剔除
- var empty:Array = my_array.duplicate()
- empty.clear() # empty = []
复制代码 除了上述的运算以外,数组还有一些其它比较实用的操作,我们接下来仍然以代码片段的形式来展示这些常用的操作:
- extends Node
- var my_array:Array = [1,2,3,4]
- # 数组的检索
- # 对目标数组调用find()方法/rfind()方法
- # find(value,from:int = 0),从索引from所指向的元素开始从左向右依次查找是否含有值value的元素,
- # 如果有则返回该元素在该数组中的索引,如果没有或该索引不存在于该数组中则返回-1
- # rfind(value,from:int = -1),从索引from所指向的元素开始从右向左依次依次查找是否含有值value的元素,
- # 如果有则返回该元素在该数组中的索引,如果没有或该索引不存在于该数组中则返回-1
- # 当from为默认值-1时,该方法等同于find_last(value)
- func find() -> void:
- print(my_array.find(4)) # 结果为3
- print(my_array.find(3,1)) # 结果为2
- print(my_array.rfind(2)) # 结果为1,等同于find_last(2)
- # 数组的含有检测
- # 对目标数组调用has()方法
- # has(value),如果该数组中含有值为value的元素,则结果为true,否则结果为false
- # 等价于:value in <Array>
- func has() -> void:
- print(my_array.has(2)) # 结果为true
- print(1 in my_array) # 结果为true
- print(my_array.has(5)) # 结果为false
- # 数组的空置检测
- # 对目标数组调用empty()方法,如果该数组不含任何元素(即该数组为空数组),则结果为true,否则结果为false
- func empty() -> void:
- print(my_array.empty()) # 结果为false
- # 获取数组中的头元素和尾元素
- # 对数组调用front()方法/back()方法
- # front()方法用于获取该数组中的第一个元素,而back()方法则用于获取该数组中的最后一个元素
- func head_and_end() -> void:
- print(my_array.front()) # 结果为1
- print(my_array.back()) # 结果为4
- # 数组的敲除
- # 对目标数组调用pop_front()方法/pop_at()方法/pop_back()方法
- # pop_front()将该数组中的第一个元素敲除出该数组,并将其作为返回值使用,同时使调用该方法的数组发生变化
- # pop_at(index:int)将该数组中索引为index的元素敲除出该数组,并将其作为返回值使用,同时使调用该方法的数组发生变化
- # pop_back()将该数组中的最后一个元素敲除出该数组,并将其作为返回值使用,同时使调用该方法的数组发生变化
- func pop() -> void:
- print(my_array.duplicate().pop_front()) # 结果为1,此时数组为[2,3,4]
- print(my_array.duplicate().pop_at(2)) # 结果为3,此时数组为[1,2,4]
- print(my_array.duplicate().pop_back()) # 结果为4,此时数组为[1,2,3]
- # 数组的长度/大小
- # 对目标数组调用size(),返回该数组所含的元素数
- func size() -> void:
- print(my_array.size()) # 结果为4
复制代码 前面我们也只是对一个数组内的某一个数值进行了引用或调用,然而实际上,我们也希望能够一次性对这个数组内的某些或全部元素进行访问。这个时候,我们就需要借助for循环体语法来帮助我们达成这个目的。
利用for循环来遍历访问一个数组内的所有元素的代码如下:
- var my_array:Array = [1,2,3,4]
- for i in my_array:
- <codes>
复制代码 其中关键字in后面必须是你要遍历的数组,且这个方法只能遍历访问其全体元素,要想限制访问一部分元素,你需要配合使用continue关键字或break关键字来控制循环体的流程,这里就还请各位同学自行学习、思考与探究。
下面用一段代码来展示遍历访问一个数组的例子:
- extends Node
- var my_array:Array = [1,2,3,4]
- func _ready() -> void:
- for i in my_array:
- print(i) # 会将1、2、3、4依次打印到控制台上
复制代码 实际上,GDScript并不像Java等强类型语言那样对数组内每个元素的数据类型进行硬性限制,因此,你的数组里可以混入其它数据类型的元素:
- var my_array:Array = [1,2.8,Vector2.ONE,"hello",'a',PackedScene]
复制代码 但在Godot 3.X中,除导出数组外并没有限定数组可容纳的数据类型的语法,因此,在声明一个不固定数据类型的数组的时候一定要注意你所声明的数组所要容纳的数据类型(换句话说,一定要注意你所声明的数组内每个元素其对应的数据类型),否则可能会出现隐患。
虽然直接声明在脚本内部的数组在Godot 3.X版本中并不能限制数据类型,但是导出数组却可以限定该数组内元素的数据类型,其语法如下:
- export(Array,<数据类型>)var my_array:Array = [value1,value2,...,valueN]
复制代码 此时,该数组内的所有元素都是属于该数据类型的数据,这样,我们就不会让元素因其数据类型错误而导致程序报错了。
既然数组可以容纳单个数值,那它是否也可以容纳其它数组或者字典等容器呢?答案是可以的。
如果一个数组内又包含了另一个数组,我们就称这个数组是一个高次数组/多维数组(Multi-dimension array),反之则称这个数组是个一次数组/一维数组。
如果是形如
- var my_array:Array = [[1,2], [3,4]]
复制代码 这样含有两层数组嵌套(即第一层是最外侧的[[1,2], [3,4]],第二层则分别是内部的[1,2]和[3,4])的数组,则我们称之为二次数组/二维数组
类似地,形如
- var my_array:Array = [[[1,2], [3,4]], [[5,6], [7,8]]]
复制代码 这样含有三层数组嵌套的数组,我们就称其为三次数组/三维数组
出于可读性起见,我建议各位同学在初次写高次数组的时候养成随手换行的习惯。如下面这个例子
- var my_array:Array = [[1,2], [3,4]]
复制代码 如果换行则是这样的视觉呈现
- var my_array:Array = [
- [1,2], [3,4]
- ]
- # 亦或是
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
复制代码 对于三次数组来说同理
- var my_array:Array = [
- [
- [1,2], [3,4]
- ],
- [
- [5,6], [7,8]
- ]
- ]
- # 亦或是
- var my_array:Array = [
- [
- [1,2],
- [3,4]
- ],
- [
- [5,6],
- [7,8]
- ]
- ]
复制代码 这样就会使开发者更加直观地了解到这个高次数组的架构
对于一个高次数组来说,其外层数组中的(某些)元素是数组,因此,如果我们直接按照引调/访问一个一次数组的方法来访问一个高次数组的元素,那么它最终并不会返回其更加深层次的元素,如下面这段代码:
- extends Node
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
- func _ready() -> void:
- print(my_array[0])
- # 返回的是[1,2]而不是1或者2
复制代码 因此,如果想要调用一个高次数组的内数组的值,则需要这么写:
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
- func _ready() -> void:
- print(my_array[0][1]) # 结果为2
- print(my_array[1][1]) # 结果为4
复制代码 对于高次数组内深层次的元素的访问,需要如下编写:
- my_array[index1][index2]...[indexN]
复制代码 其中,中括号对的数量表示访问的次数/维数(Dimension)/深度(Depth),index1表示最外层数组内的对应元素的索引,indexN表示在前面一层数组内对应元素的索引
对于一个二次数组而言,如果其访问的维数为1,则访问到的就是单个数值或数组;如果其维数为2,则其访问到的就是单个数值
对于一个三次数组而言,如果其访问的维数为1或2,则访问到的就是单个数值或数组;如果其维数为3,则其访问到的就是单个数值
前面我们已经学过了一次数组的拷贝,其目的是防止在声明一个数组并使其引用自另一个数组的时候造成这两个数组相互绑定。
但是,对于高次数组来说,只是简单的duplicate()还远远不够,看下面这个例子:
- extends Node
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
- var my_array2:Array = my_array.duplicate()
- func _ready() -> void:
- my_array2[1][0] = 5
- print(my_array)
- # 结果是[[1,2],[5,4]]
复制代码 这是因为,GDScript中的duplicate()中如果不写任何参数,则默认只是对该数组进行了浅层复制,其内部的数组(包括后面要讲到的字典),都会与源数组进行相互绑定。
为了防止这一现象的出现,我们需要更加深层地复制源数组。方法很简单,就是向duplicate()方法中输入参数true即可:
- extends Node
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
- var my_array2:Array = my_array.duplicate(true)
- func _ready() -> void:
- my_array2[1][0] = 5
- print(my_array)
- # 结果是[[1,2],[3,4]]
复制代码 这样就不会出现上上个例子中出现的问题啦~
我们前面学过了一次数组的遍历访问,是使用for循环体来实现的。对于高次数组而言,我们依旧可以使用for循环体来对其内部数组的元素进行访问
- var my_array:Array = [
- [1,2],
- [3,4]
- ]
- func _ready() -> void:
- for i in my_array:
- for j in i: # 因为这个时候我们能确定i就是数组,所以j是可以遍历i内的元素的
- print(j) # 将1、2、3、4依次打印到控制台上
复制代码 本质上就是嵌套使用for循环来逐次遍历内部数组的元素。
注:这种方法只适用于纯高次数组,即只有数组作为外层数组的元素的高次数组。如果是有非数组数值的高次数组,这种方法可能会出问题,需要借助is Array来判断是否为数组,进而配合continue关键字来进行流程控制
以上就是关于数组的使用方法了。下一节我们将会学习另一个数据类型——字典(Dictionary)的使用
|
|