赞
踩
slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
数组是定长的,长度定义好之后,不能再更改。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int 和 [4]int 就是不同的类型
而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。
数组就是一片连续的内存, slice 实际上是一个结构体,包含三个字段:长度、容量、底层数组。
- // runtime/slice.go
- type slice struct {
- array unsafe.Pointer // 元素指针
- len int // 长度
- cap int // 容量
- }
slice 的数据结构如下:
注意,底层数组是可以被多个 slice 同时指向的,因此对一个 slice 的元素进行操作是有可能影响到其他 slice 的。
【引申1】 [3]int 和 [4]int 是同一个类型吗?
不是。因为数组的长度是类型的一部分,这是与 slice 不同的一点。
【引申2】 下面的代码输出是什么?
说明:例子来自雨痕大佬《Go学习笔记》第四版,P43页。这里我会进行扩展,并会作图详细分析。
- package main
-
- import "fmt"
-
- func main() {
- slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
- s1 := slice[2:5]
- s2 := s1[2:6:7]
-
- s2 = append(s2, 100)
- s2 = append(s2, 200)
-
- s1[2] = 20
-
- fmt.Println(s1)
- fmt.Println(s2)
- fmt.Println(slice)
- }
结果:
- [2 3 20]
- [4 5 6 7 100 200]
- [0 1 2 3 20 5 6 7 100 9]
s1 从 slice 索引2(闭区间)到索引5(开区间,元素真正取到索引4),长度为3,容量默认到数组结尾,为8。s2 从 s1 的索引2(闭区间)到索引6(开区间,元素真正取到索引5),容量到索引7(开区间,真正到索引6),为5。
接着,向 s2 尾部追加一个元素 100:
s2 = append(s2, 100)
s2 容量刚好够,直接追加。不过,这会修改原始数组对应位置的元素。这一改动,数组和 s1 都可以看得到。
再次向 s2 追加元素200:
s2 = append(s2, 100)
最后,修改 s1 索引为2位置的元素:
s1[2] = 20
这次只会影响原始数组相应位置的元素。它影响不到 s2 了,人家已经远走高飞了。
再提一点,打印 s1 的时候,只会打印出 s1 长度以内的元素。所以,只会打印出3个元素,虽然它的底层数组不止3个元素。
slice 其实是一个结构体,包含了三个成员:len, cap, array。分别表示切片长度,容量,底层数据的地址
通过 slice 的 array 字段就可以拿到数组的地址。在代码里,是直接通过类似 s[i]=10 这种操作改变 slice 底层数组元素值。
另外,值得注意的是,Go 语言的函数参数传递,只有值传递,没有引用传递。
来看一个代码片段:
- package main
-
- func main() {
- s := []int{1, 1, 1}
- f(s)
- fmt.Println(s)
- }
-
- func f(s []int) {
- // i只是一个副本,不能改变s中元素的值
- /*for _, i := range s {
- i++
- }
- */
-
- for i := range s {
- s[i] += 1
- }
- }
运行一下,程序输出:
[2 2 2]
果真改变了原始 slice 的底层数据。这里传递的是一个 slice 的副本,在 f 函数中,s 只是 main 函数中 s 的一个拷贝。在f 函数内部,对 s 的作用并不会改变外层 main 函数的 s。面试官:说下Golang Slice的底层实现,泪崩了!
要想真的改变外层 slice,只有将返回的新的 slice 赋值到原始 slice,或者向函数传递一个指向 slice 的指针。我们再来看一个例子:
- package main
-
- import "fmt"
-
- func myAppend(s []int) []int {
- // 这里 s 虽然改变了,但并不会影响外层函数的 s //来源公众号:码农编程进阶笔记 s = append(s, 100)
- return s
- }
-
- func myAppendPtr(s *[]int) {
- // 会改变外层 s 本身
- *s = append(*s, 100)
- return
- }
-
- func main() {
- s := []int{1, 1, 1}
- newS := myAppend(s)
-
- fmt.Println(s)
- fmt.Println(newS)
-
- s = newS
-
- myAppendPtr(&s)
- fmt.Println(s)
- }
运行一下,程序输出:
- [1 1 1]
- [1 1 1 100]
- [1 1 1 100 100]
myAppend 函数里,虽然改变了 s,但它只是一个值传递,并不会影响外层的 s,因此第一行打印出来的结果仍然是 [1 1 1]。
而 newS 是一个新的 slice,它是基于 s 得到的。因此它打印的是追加了一个 100 之后的结果:[1 1 1 100]。
最后,将 newS 赋值给了 s,s 这时才真正变成了一个新的slice。之后,再给 myAppendPtr 函数传入一个 s 指针,这回它真的被改变了:[1 1 1 100 100]。
~~~~~推荐阅读~~~~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。