0%

一行 make([][]int, 3),Go 到底造出了什么?

一行 make([][][][]int, 3),Go 到底造出了什么?

在 Go 里创建一个 3 行 4 列的二维切片,标准写法是这样的:

1
2
3
4
5
6
rows, cols := 3, 4

grid := make([][]int, rows)
for i := 0; i < rows; i++ {
grid[i] = make([]int, cols)
}

最让人困惑的往往是第一行:make([][]int, rows) 到底造了什么?为什么不能一步到位,非要再写个循环?

问一:[][]int 应该怎么读?

把它拆成两半:

1
2
3
[]  []int
↑ ↑
切片 每个元素的类型

前面的 [] 表示“这是一个切片”,后面的 []int 是“每个元素的类型”。

所以 [][]int 读作元素为 []int 的切片。Go 里其实没有真正的“二维切片”,只有“切片套切片”。


问二:make([][]int, 3) 的结果是什么?

先看熟悉的写法。make([]T, n) 会造一个长度为 n 的切片,每个元素都是 T 类型的零值。

写法 元素类型 零值 结果
make([]int, 3) int 0 [0 0 0]
make([]string, 3) string "" ["" "" ""]
make([]bool, 3) bool false [false false false]
make([][]int, 3) []int nil [nil nil nil]

最后一行和前三行的规则一样,只是 T 换成了 []int,而 []int 的零值是 nil。执行完这一句,内存里是这样的:

可以验证一下:

1
2
3
4
grid := make([][]int, 3)
fmt.Println(grid) // [[] [] []]
fmt.Println(len(grid)) // 3
fmt.Println(len(grid[0])) // 0 ← 这一行还是空的

也就是说,这一句只定下了“有 3 行”,没说每行有几列。

问三:后面的循环在做什么?

给每一行单独分配 4 个 int,再把这一行挂到 grid[i] 上。

i = 0 时:

i = 1 时:

i = 2 时:

有两点要注意:

  1. 外层格子里存的是指向那一行的引用(指针、长度、容量),数据本身在那一行里。
  2. 每一行都是单独 make 出来的,是各自独立的内存,互不影响。

问四:如果漏掉循环会怎样?

访问 grid[1][2] 时要走两跳:

如果没有循环,grid[1] 还是 nil,长度为 0,第二跳就会直接 panic:

1
panic: runtime error: index out of range [2] with length 0

打个比方:make([][]int, 3) 只搭好了 3 层空书架,循环里的 make([]int, 4) 才是往每一层放进一个装得下 4 本书的盒子。只搭了隔板就去拿“第 1 层第 2 本书”,当然拿不到。

问五:每一行必须一样长吗?

不必。每一行都是单独 make 出来的切片,长度可以不同,内存也不一定连续。这种结构通常叫“锯齿数组”,杨辉三角就很适合用它来存:

1
2
3
4
tri := make([][]int, 4)
for i := range tri {
tri[i] = make([]int, i+1) // 第 i 行有 i+1 个元素
}
1
2
3
4
tri[0] ──▶ [1]
tri[1] ──▶ [1 1]
tri[2] ──▶ [1 2 1]
tri[3] ──▶ [1 3 3 1]

一句话总结

make([][]int, 3) 只造出了 3 个空的行引用,循环里的 make([]int, 4) 才真正给每一行分配了空间。

切片只是指向底层数组的窗口,二维切片就是一排这样的窗口,每个窗口通向自己的一行。