声明数组
数组类型写作 [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。