Skip to content

数组

数组Array,是一种有序泛型序列,它可以存储多个同类型数据,元素的顺序是固定的,添加或删除操作会影响后续元素的位置。

数组中的值,我们将其称之为“元素(element)”

数组是最常用也最灵活的容器之一,适合存储一组顺序排列的数据。在遍历、查找、批量处理等场景中非常好用,但在插入/删除操作频繁时就要注意性能开销

数组的默认值为空数组[],可由紧缩数组转换而来

特别的是,数组的!===运算符并非比较数组的引用,而是用于比较数组的内容,如果完全相等(数组长度与元素位置和值)则==返回true!=返回false拥有相同的元素但顺序不同会认为不相同

数组也能使用+=/+运算符来拼接

赋值

向数组赋值时,我们需要使用方括号包裹元素,用英文逗号间隔每一个元素

gdscript
var arr = [] # 声明空数组
arr = [1, 2, 3] # 为arr赋予一个新的数组并填充三个元素

一对方括号即代表一个数组实例,表示构造一个数组,如果方括号内有元素,则按照从左到右的顺序填充数组

也就是说,上面的代码实际上等价于:

gdscript
var arr = Array()
arr = Array()
arr.append(1)
arr.append(2)
arr.append(3)

无类型数组可以填充任意类型的元素

gdscript
var arr: Array = ["第一", 2, 3, "最后"]

访问

数组可以动态调整大小,使用索引来表示元素在数组中的位置,其索引从0开始,索引为负整数时则表示从数组尾部开始计数

正向访问时,索引应控制在0~ 数组长度 - 1

逆向访问时,索引应控制在-1 ~ -数组长度

超过索引范围时,会造成越界访问

数组长度也就是数组内元素的数量

访问数组指定元素时,使用如下语法:

gdscript
var array = ["第一", 2, 3, "最后"]
print(array[0])  # 输出“第一”
print(array[2])  # 输出 3
print(array[-1]) # 输出“最后”

除了使用常量,也可以在方括号中填入整数变量或表达式

gdscript
array[array.size() - 1] # 最后一个元素
var index = 0
array[index] # 第一个元素

如果数组不是只读的,你还可以通过索引来修改指定位置的元素

gdscript
var array = ["第一", 2, 3, "最后"]
array[1] = "第二"
print(array[1])  # 输出“第二”
print(array[-3]) # 输出“第二”

常量const定义的数组就是只读数组,不能删改数组的元素,const定义的数组也不能将一个新的数组实例赋给它,但是var定义的只读数组可以通过将一个新的数组实例赋给它解除只读状态

通过索引访问数组,可以将该元素取出,修改后将元素放回,但本质上和直接修改元素是一样的效果

gdscript
var arr = [1, 2, 3]
var a = arr[0]
a = 10
arr[0] = a

如果数组元素是对象,取出对象修改属性后不必再放回数组,因为对象按引用传递,除非你更改了该对象的实例

  • 取出对象并修改name属性,下面的代码将会输出"Node1"

    gdscript
    var arr = [Node.new(), Node.new(), Node.new()]
    var node = arr[0]
    node.name = "Node1"
    print(arr[0].name)
  • 取出对象,赋予一个新的实例并修改name属性,下面的代码将会输出空字符串而非"Node1"

    你可能想问“引用类型不是按引用传递吗,修改node不是应该会修改原本的对象吗?”

    关于这个问题,见下面的 数组遍历 部分

    gdscript
    var arr = [Node.new(), Node.new(), Node.new()]
    var node = arr[0]
    node = Node.new()
    node.name = "Node1"
    print(arr[0].name)
  • 你也可以不取出元素,直接修改对象的属性,下面的代码将会输出"Node1"

    gdscript
    var arr = [Node.new(), Node.new(), Node.new()]
    arr[0].name = "Node1"
    print(arr[0].name)

遍历

需要访问数组的每一个元素时,可以使用以下方法

注意:不要在遍历数组时更改元素数量,这样做会造成预期外的行为!

gdscript
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() 复制副本来遍历。

  • 正序遍历直接访问元素:

    gdscript
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    for i in arr:
        print(i)
  • 逆向遍历直接访问元素:

    复制一份原数组,然后翻转复制后的数组再遍历

    gdscript
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]    
    var copy = arr.duplicate()
    copy.reverse()
    for i in copy:
        print(i)
  • 正序遍历通过索引访问元素:

    gdscript
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    for i in range(0, arr.size(), 1):
        print(arr[i])
  • 逆序遍历通过索引访问元素:

    gdscript
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    for i in range(arr.size() - 1, -1, -1):
        print(arr[i])

需要注意的是,不通过索引访问数组元素时,迭代变量实际上是元素的副本,如果元素是引用类型,则迭代变量是对象的引用地址的副本

修改迭代变量的属性会影响数组内的对象,但为其赋予一个新的实例(new或null)不会影响到数组内的对象

想要在迭代时修改元素的值,就直接使用索引遍历

gdscript
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 中,数组属于引用类型,这意味着:

把数组赋值给一个新变量,或传递给函数参数时,它们会共享同一份数据

也就是说,无论在哪个变量里修改了数组内容,所有引用这个数组的地方都会受到影响

看看下面这个例子:

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 的第一个元素也变了,因为它们其实引用的是同一份数组。

这个特性在函数中同样适用:

gdscript
func modify(arr: Array) -> void:
    arr[0] = "第一位"

modify(arr_2)
print(arr)    # 输出:["第一位", 2, 3, 4, 5]
print(arr_2)  # 输出:["第一位", 2, 3, 4, 5]

想要避免这种“连带修改”的情况,你可以使用 duplicate() 方法,来生成一个副本:

gdscript
var arr_3 = arr.duplicate()
arr_3[0] = "新的第一位"
print(arr)   # 保持不变
print(arr_3) # 改变的是副本

duplicate(true) 支持递归复制嵌套内容(例如数组中包含数组、字典等),否则只会复制最外层数组本身。

嵌套结构

数组支持将其他集合作为数组的元素以表达更为复杂的结构

gdscript
var matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

print(matrix[1][2]) # 输出6,访问第二行第三列

典型的案例是

  • 二维表格、棋盘地图

  • JSON 格式的数据(例如:一个列表里每一项都是字典)

gdscript
var json_array = [
    {"name": "Alice", "score": 95},
    {"name": "Bob", "score": 88},
    {"name": "Cathy", "score": 92}
]
print(json_array[0]["name"]) # 输出 "Alice"

注意:如果你复制了一个嵌套数组但没有开启递归(duplicate(true)),那么嵌套部分仍然是共享的!

gdscript
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() 方法

gdscript
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]是例外,不过非类型化数组的赋值是不安全的

gdscript
var a: Array[Node2D] = [Node2D.new()]
var b: Array[Variant] = a
var c: Array = a

练习

可以参见数组后篇"方法"来辅助完成练习

  1. 编写一个函数,接收一个数组,将其中所有的偶数替换为字符串"偶数",并打印最终的数组

    gdscript
    # 输入:[1, 2, 3, 4, 5]
    # 输出:[1, "偶数", 3, "偶数", 5]
  2. 编写一个函数,接收一个整数数组,返回其中的最大值。如果数组为空,返回null

    gdscript
    # 输入:[3, 17, 8, 6]
    # 输出:17
  3. 编写一个函数,过滤数组中的nullfalse,保留true和其他非空值,返回新的数组副本

    gdscript
    # 输入:[null, 0, false, "hello", true]
    # 输出:[0, "hello", true]
  4. 给定一个二维数组,请将其中每一行的顺序翻转后输出。

    gdscript
    # 输入:[[1, 2, 3], [4, 5, 6]]
    # 输出:[[3, 2, 1], [6, 5, 4]]
  5. 给定一个学生分数组成的数组,请打印及格人数与总人数,并计算及格率(分数≥60算及格)。

    gdscript
    # 输入:[59, 62, 85, 47, 90, 73]
    # 输出:
    # 及格人数:4
    # 总人数:6
    # 及格率:0.6666667
  6. 请写一段代码,打印下面这个地图中所有非 0 的坐标和值。

    gdscript
    var map = [
        [0, 1, 0],
        [2, 0, 3],
        [0, 4, 0]
    ]
    # 输出格式:
    # 值 1 出现在 (0, 1)
    # 值 2 出现在 (1, 0)
    # 值 3 出现在 (1, 2)
    # 值 4 出现在 (2, 1)