数组
数组Array,是一种有序泛型序列,它可以存储多个同类型数据,元素的顺序是固定的,添加或删除操作会影响后续元素的位置。
数组中的值,我们将其称之为“元素(element)”
数组是最常用也最灵活的容器之一,适合存储一组顺序排列的数据。在遍历、查找、批量处理等场景中非常好用,但在插入/删除操作频繁时就要注意性能开销
数组的默认值为空数组[],可由紧缩数组转换而来
特别的是,数组的!=和==运算符并非比较数组的引用,而是用于比较数组的内容,如果完全相等(数组长度与元素位置和值)则==返回true,!=返回false。拥有相同的元素但顺序不同会认为不相同
数组也能使用+=/+运算符来拼接
赋值
向数组赋值时,我们需要使用方括号包裹元素,用英文逗号间隔每一个元素
var arr = [] # 声明空数组
arr = [1, 2, 3] # 为arr赋予一个新的数组并填充三个元素一对方括号即代表一个数组实例,表示构造一个数组,如果方括号内有元素,则按照从左到右的顺序填充数组
也就是说,上面的代码实际上等价于:
var arr = Array()
arr = Array()
arr.append(1)
arr.append(2)
arr.append(3)无类型数组可以填充任意类型的元素
var arr: Array = ["第一", 2, 3, "最后"]访问
数组可以动态调整大小,使用索引来表示元素在数组中的位置,其索引从0开始,索引为负整数时则表示从数组尾部开始计数
正向访问时,索引应控制在0~ 数组长度 - 1
逆向访问时,索引应控制在-1 ~ -数组长度
超过索引范围时,会造成越界访问
数组长度也就是数组内元素的数量
访问数组指定元素时,使用如下语法:
var array = ["第一", 2, 3, "最后"]
print(array[0]) # 输出“第一”
print(array[2]) # 输出 3
print(array[-1]) # 输出“最后”除了使用常量,也可以在方括号中填入整数变量或表达式
array[array.size() - 1] # 最后一个元素
var index = 0
array[index] # 第一个元素如果数组不是只读的,你还可以通过索引来修改指定位置的元素
var array = ["第一", 2, 3, "最后"]
array[1] = "第二"
print(array[1]) # 输出“第二”
print(array[-3]) # 输出“第二”常量
const定义的数组就是只读数组,不能删改数组的元素,const定义的数组也不能将一个新的数组实例赋给它,但是var定义的只读数组可以通过将一个新的数组实例赋给它解除只读状态
通过索引访问数组,可以将该元素取出,修改后将元素放回,但本质上和直接修改元素是一样的效果
var arr = [1, 2, 3]
var a = arr[0]
a = 10
arr[0] = a如果数组元素是对象,取出对象修改属性后不必再放回数组,因为对象按引用传递,除非你更改了该对象的实例
取出对象并修改
name属性,下面的代码将会输出"Node1"gdscriptvar arr = [Node.new(), Node.new(), Node.new()] var node = arr[0] node.name = "Node1" print(arr[0].name)取出对象,赋予一个新的实例并修改
name属性,下面的代码将会输出空字符串而非"Node1"你可能想问“引用类型不是按引用传递吗,修改node不是应该会修改原本的对象吗?”
关于这个问题,见下面的 数组遍历 部分
gdscriptvar arr = [Node.new(), Node.new(), Node.new()] var node = arr[0] node = Node.new() node.name = "Node1" print(arr[0].name)你也可以不取出元素,直接修改对象的属性,下面的代码将会输出"Node1"
gdscriptvar arr = [Node.new(), Node.new(), Node.new()] arr[0].name = "Node1" print(arr[0].name)
遍历
需要访问数组的每一个元素时,可以使用以下方法
注意:不要在遍历数组时更改元素数量,这样做会造成预期外的行为!
var arr = [1, 2, 3, 4, 5]
# 遍历时删除元素可能会跳过元素
for i in arr:
arr.erase(i)
print(arr) # 并非输出空数组
# 遍历时添加元素可能会造成无限循环
for i in arr:
arr.append(i)
print(arr)如果确实要在遍历时删除/添加元素,建议先用 Array.duplicate() 复制副本来遍历。
正序遍历直接访问元素:
gdscriptvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] for i in arr: print(i)逆向遍历直接访问元素:
复制一份原数组,然后翻转复制后的数组再遍历
gdscriptvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] var copy = arr.duplicate() copy.reverse() for i in copy: print(i)正序遍历通过索引访问元素:
gdscriptvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] for i in range(0, arr.size(), 1): print(arr[i])逆序遍历通过索引访问元素:
gdscriptvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] for i in range(arr.size() - 1, -1, -1): print(arr[i])
需要注意的是,不通过索引访问数组元素时,迭代变量实际上是元素的副本,如果元素是引用类型,则迭代变量是对象的引用地址的副本
修改迭代变量的属性会影响数组内的对象,但为其赋予一个新的实例(new或null)不会影响到数组内的对象
想要在迭代时修改元素的值,就直接使用索引遍历
var arr = [Node.new(), Node.new(), Node.new()]
for node in arr:
node = null
print(arr) # 可能输出 [<Node#26709329241>, <Node#26726106458>, <Node#26742883675>]
for i in arr.size():
arr[i] = null
print(arr) # 输出 [<null>, <null>, <null>]传递数组(按引用)
在 GDScript 中,数组属于引用类型,这意味着:
把数组赋值给一个新变量,或传递给函数参数时,它们会共享同一份数据
也就是说,无论在哪个变量里修改了数组内容,所有引用这个数组的地方都会受到影响。
看看下面这个例子:
var arr = [1, 2, 3, 4, 5]
var arr_2 = arr
arr_2[0] = 0
print(arr_2) # 输出[0, 2, 3, 4, 5]
print(arr) # 输出[0, 2, 3, 4, 5]我们只是修改了 arr_2[0],但 arr 的第一个元素也变了,因为它们其实引用的是同一份数组。
这个特性在函数中同样适用:
func modify(arr: Array) -> void:
arr[0] = "第一位"
modify(arr_2)
print(arr) # 输出:["第一位", 2, 3, 4, 5]
print(arr_2) # 输出:["第一位", 2, 3, 4, 5]想要避免这种“连带修改”的情况,你可以使用 duplicate() 方法,来生成一个副本:
var arr_3 = arr.duplicate()
arr_3[0] = "新的第一位"
print(arr) # 保持不变
print(arr_3) # 改变的是副本duplicate(true) 支持递归复制嵌套内容(例如数组中包含数组、字典等),否则只会复制最外层数组本身。
嵌套结构
数组支持将其他集合作为数组的元素以表达更为复杂的结构
var matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[1][2]) # 输出6,访问第二行第三列典型的案例是
二维表格、棋盘地图
JSON 格式的数据(例如:一个列表里每一项都是字典)
var json_array = [
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 88},
{"name": "Cathy", "score": 92}
]
print(json_array[0]["name"]) # 输出 "Alice"注意:如果你复制了一个嵌套数组但没有开启递归(duplicate(true)),那么嵌套部分仍然是共享的!
var original = [[1, 2], [3, 4]]
var shallow = original.duplicate() # 浅复制
var deep = original.duplicate(true) # 深复制
shallow[0][0] = 999
print(original[0][0]) # 也变成了999!因为只是浅复制
deep[0][0] = 111
print(original[0][0]) # 还是999,不受影响类型化数组
类型化的数组只能包含给定类型的元素,或者从给定类继承的元素
类型化数组限定该数组只能存储何种类型的数据,是静态类型数组,通过使用Array[Type]来指定
其中,Type支持限定的类型有:
Variant类型:Array[Variant]内置类型:
Array[Node],Array[int]用户自定义类型:
Array[MyClass]枚举类型:
Array[MyEnum]
无类型数组Array等价于Array[Variant]
类型化数组不支持嵌套类型化集合,比如这样是不被允许的:Array[Array[int]],Array[Dictionary[String, int]]
但你可以改用Array[Array]
类型化数组限定了数组可接收的元素的类型,你不能为数组的元素赋予不同类型的值,但数组所接收类型的子类型除外
不能直接将类型化数组赋值给不同类型的类型化数组,即使该类型是数组所接收类型的子类型
若需要对类型化数组进行转换,可以创建一个新数组,并使用 Array.assign() 方法
var a: Array[Node2D] = [Node2D.new()]
# 可以直接将这个值作为数组`b`的元素,因为`Node2D`继承自`Node`
var b: Array[Node] = [a[0]]
# 错误!不能将`Array[Node2D]`赋予`Array[Node]`
b = a
# `assign()`方法,将一个数组内的元素赋值给另一个数组,而非该数组的引用(浅拷贝),如果是类型化数组可以自动转换元素类型
b.assign(a)Array/Array[Variant]是例外,不过非类型化数组的赋值是不安全的
var a: Array[Node2D] = [Node2D.new()]
var b: Array[Variant] = a
var c: Array = a练习
可以参见数组后篇"方法"来辅助完成练习
编写一个函数,接收一个数组,将其中所有的偶数替换为字符串
"偶数",并打印最终的数组gdscript# 输入:[1, 2, 3, 4, 5] # 输出:[1, "偶数", 3, "偶数", 5]编写一个函数,接收一个整数数组,返回其中的最大值。如果数组为空,返回
nullgdscript# 输入:[3, 17, 8, 6] # 输出:17编写一个函数,过滤数组中的
null和false,保留true和其他非空值,返回新的数组副本gdscript# 输入:[null, 0, false, "hello", true] # 输出:[0, "hello", true]给定一个二维数组,请将其中每一行的顺序翻转后输出。
gdscript# 输入:[[1, 2, 3], [4, 5, 6]] # 输出:[[3, 2, 1], [6, 5, 4]]给定一个学生分数组成的数组,请打印及格人数与总人数,并计算及格率(分数≥60算及格)。
gdscript# 输入:[59, 62, 85, 47, 90, 73] # 输出: # 及格人数:4 # 总人数:6 # 及格率:0.6666667请写一段代码,打印下面这个地图中所有非 0 的坐标和值。
gdscriptvar map = [ [0, 1, 0], [2, 0, 3], [0, 4, 0] ] # 输出格式: # 值 1 出现在 (0, 1) # 值 2 出现在 (1, 0) # 值 3 出现在 (1, 2) # 值 4 出现在 (2, 1)