huangwenwei - 字里行间 | Golang 中的数组 (array) 和切片 (slice)
中文描述约定
array: 数组
slice: 切片
来做个测试,不在机器上运行下面的代码,请给出输出
vals :make([]int, 5)
for i:= 0; i < 5; i++ {
vals = append(vals, i)
}
fmt.Println(vals)
如果你猜测的结果是 [0 0 0 0 0 1 2 3 4]
,恭喜你答对了。
为啥么正确答案不是 [0 1 2 3 4]
呢?
大部分像我一样 Golang 初学者都会打错这道测试题,在这篇文章中,我们来探讨一下为什么不是我们期望的输出结果以及 slice 数据类型中 capacity 和 length 的用法。
Slices VS Arrays
初学时可能会把 Golang 中数组和切片的区别,但是如果习惯了使用切片,将会极大地优化代码;
数组和切片有很多不同点,但在这里我们记住它们之间最大的区别就是:固定的数组大小是数组声明时的一部分,而切片可以有一个动态的大小。那么在实际的编码中是如何体现的呢?例如 val a [10]int
, 这个数组有一个固定的大小 10 ,却不能改变长其度;如果我们调用 len(a)
,返回 10,因为长度就是数组的一部分;这就导致一个问题,如果有数组长度超过 10 的需求,你必须新建另外一个数组对象,譬如 val b [11]int
, 并把数组 a 的值拷贝到数组 b
在某种场景下固定长度的数组非常使用,但是通常我们长度灵活的数组,需要一种“没有固定大小的数组”,最直接的做法是声明数组时给足够大的长度,例如:
var vals [20]int
for i := 0; i < 5; i++ {
vals[i] = i * i
}
subsetLen := 5
fmt.Println("The subset of our array has a length of:" subsetLen)
// Add a new item to our array
vals[subsetLen] = 123
subsetLen++
fmt.Print("The subset of our array has a length of:", subsetLen)
上面的例子中,我们声明数组时设置了其长度为 20 ,通过一个变量我们“设置”其长度为 5 ,我们往其加了一个元素之后, subset 自增 1,数组长度就变成了 6
粗略地说,这就是切片的原理,通过设置一个值来限定数组的最大长度,在上述例子中为 20 ;同时切片还有一个值来表示数组元素的真实长度,在上述例子中为 subsetLen ;故切片中有 capacity cap 和 length len 两个概念。切片的内部实现基于数组,但是使用方式更灵活、运行更高效。cap 限定了切片中元素个数增加的上限,通过 append
函数,我们不需关心数组底层的个数上限。
回到我们的测试题
明白了数组和切片的概念和不同点之后,我们再次解释一下测试题。
make
函数能够声明一个切片,第一个参数表示类型,第二个参数表示类型的长度,第三个参数为可选参数,表示类型的容纳上限;
make([]int, 5)
表示我们要新建一个长度为 5 的切片,切片的默认容纳上限(capacity)和长度一致。但是 append
函数表示切片增加一个元素,那么该切片的容纳上限将同时增加,并在切片的末尾新增一个元素。也就是默认的切片值为 [0 0 0 0 0]
增加一个元素将会被放置在在第五个元素后面,所以运行结果如下:
vals := make([]int, 5)
fmt.Println("Capacity was:", cap(vals))
for i := 0; i < 5; i++ {
vals = append(vals, i)
fmt.Println("Capacity is now:", cap(vals))
}
fmt.Println(vals)
// output: [0 0 0 0 0 0 1 2 3 4]
如果修改为下面的代码,则输出为 [0, 1, 2, 3, 4]
vals := make([]int, 5)
for i := 0; i < 5; i++ {
vals[i] = i
}
fmt.Println(vals)
代码不够简洁,改造一下:我们把 vals 的长度设置为 0 ,容纳上限为 5
vals := make([]int, 0, 5)
for i := 0; i < 5; i++ {
vals.appen(i)
}
fmt.Println(vals)
最后,如果能知道切片的上限,建议最好给 make 函数加上第三个参数。
有疑问加站长微信联系(非本文作者)