Menu

Golang 数组:固定长度、值语义以及与切片的区别

Go 数组的长度是固定的,并且是类型的一部分,赋值时整个数组会被复制。学习如何声明、遍历、比较和传递数组,以及为什么大多数 Go 代码改用切片。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

声明数组

数组类型写作 [N]T:先写长度,再写元素类型。长度在编译期就已确定,并且是类型的一部分。

输出:

[90 0 0 0 75] 5
Tue
[5]int [2 3 5 7 11]
["" "b" "" "d" ""]

数组总是完整初始化的。没有赋值的元素保存的是元素类型的零值:0、""、false、nil。

索引与越界

索引的范围是 0 到 len(a)-1。常量索引越界是编译错误,变量索引越界则会在运行时触发 panic。

var a [3]int
a[5] = 1 // compile error: invalid argument: index 5 out of bounds [0:3]

i := 5
a[i] = 1 // panic: runtime error: index out of range [5] with length 3

遍历

for i, v := range a 按顺序访问每个元素。v 是元素的副本,所以给 v 赋值不会改变数组。要修改元素,请通过索引写入。

数组还有一个特有的细节:对数组值使用 range 时,会预先求值出数组的一份副本。如果循环体修改了后面的元素,v 显示的仍然是旧值。对 &a 或 a[:] 使用 range 可以避免复制。

数组是值

这是 Go 数组与 C、Java 或 JavaScript 中数组的主要区别。给数组赋值会复制每个元素,把数组传给函数也会复制它。

输出:

[1 2 3] [99 2 3]
[1 2 3]
[0 2 3]

小数组复制起来很便宜,大数组就很昂贵了:按值传递一个 [1_000_000]int 会复制 8 MB 数据。当数组很大或函数需要修改它时,请传递指针(*[N]T)或切片(a[:])。通过数组指针取索引不需要显式写 *:a[0] 可以直接用于 *[3]int。

比较数组以及用作 map 的键

元素可比较的数组支持 == 和 !=。每个元素都相等时,两个数组才相等。这也让数组可以作为 map 的键,而切片不行。

长度不同的数组是不同的类型,根本无法比较:[3]int{} == [4]int{} 无法编译(mismatched types [3]int and [4]int)。

多维数组

数组的数组可以构成一个固定大小的网格。整个网格是一块连续的内存。

row[:] 把 [3]rune 类型的行转换成 []rune 切片,而 string() 接受这种切片。

数组与切片

数组 [N]T切片 []T
长度固定,是类型的一部分随 append 变化
赋值与传参复制所有元素复制一个很小的头部,共享元素
零值N 个零值元素nil,长度为 0
==支持,逐个元素比较只能与 nil 比较
map 的键可以不可以
典型用途固定大小的数据:哈希值、坐标、缓冲区几乎所有其他场景

实际开发中,你主要会在几个地方碰到数组:sha256.Sum256 返回 [32]byte,大小永远不变的查找表,以及作为切片的底层存储。用 a[:] 或 a[1:3] 对数组切片,会得到一个与它共享内存的切片;这种共享意味着什么,见 切片 页面。

数组与切片之间的转换

把切片转换成数组(Go 1.20)或数组指针(Go 1.17)时,如果切片比数组长度短,就会 panic:panic: runtime error: cannot convert slice with length 2 to array or pointer to array with length 3。

常见错误

  • 以为函数会修改传入的数组参数。 函数拿到的是一份副本。请传递 *[N]T 或切片。
  • 在需要切片的地方使用数组。 []int{1, 2} 是切片,[2]int{1, 2} 是数组。接受 []int 的函数不接受 [2]int;请传入 a[:]。
  • 在 range 循环中遍历大数组。 for _, v := range bigArray 会先复制整个数组。请对 &bigArray 或切片使用 range。

常见问题

Go 中如何声明数组?

在元素类型前面的方括号里写上长度:var a [5]int 会创建五个零。带初始值:a := [3]string{"x", "y", "z"}。用 [...] 让编译器来数:a := [...]int{1, 2, 3} 的类型是 [3]int。

Go 中数组和切片有什么区别?

数组的长度是固定的,并且是类型的一部分([3]int 和 [4]int 是不同的类型),赋值或传参时会被复制。切片([]int)是底层数组的一个视图,长度可以通过 append 改变,复制切片只复制这个视图,不复制元素。大多数 Go 代码使用切片。

Go 中如何获取数组的长度?

使用内置函数 len(a)。对数组来说,它是一个编译期常量,所以可以用在常量表达式中。cap(a) 返回同样的数字。

Go 中可以比较数组吗?

可以,只要元素类型可比较,就能用 == 和 !=。两个数组的所有元素都相等时,它们才相等。数组还可以作为 map 的键。切片不能用 == 比较(与 nil 比较除外);比较切片请用 slices.Equal。

Coddy 编程语言插图

和 Coddy 一起学编程

开始