面试题
最近Go 101的作者发布了11道Go面试题,非常有趣,打算写一个系列对每道题做详细解析。欢迎大家关注。
大家可以看下面这道关于slice
的题目,通过这道题我们可以对slice
的特性和注意事项有一个深入理解。
package main import "fmt" func main() { a := [...]int{0, 1, 2, 3} x := a[:1] y := a[2:] x = append(x, y...) x = append(x, y...) fmt.Println(a, x) }
-
A: [0 1 2 3] [0 2 3 3 3]
-
B: [0 2 3 3] [0 2 3 3 3]
-
C: [0 1 2 3] [0 2 3 2 3]
-
D: [0 2 3 3] [0 2 3 2 3]
大家可以在评论区留下你们的答案。这道题有几个考点:
-
slice
的底层数据结构是什么?给slice
赋值,到底赋了什么内容? -
通过
:
操作得到的新slice
和原slice
是什么关系?新slice
的长度和容量是多少? -
append
在背后到底做了哪些事情? -
slice
的扩容机制是什么?
解析
我们先逐个解答上面的问题。
slice的底层数据结构
talk is cheap, show me the code
. 直接上slice
的源码:
slice
定义在src/runtime/slice.go
第15行,源码地址:go/slice.go at master · golang/go · GitHub。
Pointer
定义在src/unsafe/unsafe.go
第184行,源码地址:go/unsafe.go at master · golang/go · GitHub。
type slice struct { array unsafe.Pointer len int cap int } type Pointer *ArbitraryType
slice
实际上是一个结构体类型,包含3个字段,分别是
-
array: 是指针,指向一个数组,切片的数据实际都存储在这个数组里。
-
len: 切片的长度。
-
cap: 切片的容量,表示切片当前最多可以存储多少个元素,如果超过了现有容量会自动扩容。
因此给slice
赋值,实际上都是给slice
里的这3个字段赋值。看起来这像是一句正确的废话,但是相信我,记住这句话可以帮助你非常清晰地理解对slice
做修改后slice
里3个字段的值是怎么变的,slice
指向的底层数组的数据是怎么变的。
:
分割操作符
:
分割操作符有几个特点:
-
:
可以对数组或者slice
做数据截取,:
得到的结果是一个新slice
。 -
新
slice
结构体里的array
指针指向原数组或者原slice
的底层数组,新切片的长度是:
右边的数值减去左边的数值,新切片的容量是原切片的容量减去:
左边的数值。 -
:
的左边如果没有写数字,默认是0,右边没有写数字,默认是被分割的数组或被分割的切片的长度。a := make([]int, 0, 4) // a的长度是0,容量是4 b := a[:] // 等价于 b := a[0:0], b的长度是0,容量是4 c := a[:1] // 等价于 c := a[0:1], b的长度是1,容量是4 d := a[1:] // 编译报错 panic: runtime error: slice bounds out of range e := a[1:4] // e的长度3,容量3
-
:
分割操作符右边的数值有上限,上限有2种情况
-
如果分割的是数组,那上限是是被分割的数组的长度。
-
如果分割的是切片,那上限是被分割的切片的容量。注意</