【Go】深入剖析slice和array (2)

之前的疑问,为什么数组不能用 make 创建? 上面分析了解到数组操作是在编译时转换成对应指令的,而 make 是在运行时处理(特殊状态下会做编译器优化,make可以被优化,下面 slice 分析时来讲)。

slice

因为数组是固定长度且是值传递,很不灵活,所以在 Go 程序中很少看到数组的影子。然而 slice 无处不在,slice 以数组为基础,提供强大的功能和遍历性。
slice 的类型规范是[]T,slice T元素的类型。与数组类型不同,slice 类型没有指定的长度。

** slice 申明的几种方法:**

s := []int{1, 2, 3} 简短的赋值语句
var s []int var 申明
make([]int, 3, 8) 或 make([]int, 3) make 内置方法创建
s := ss[:5] 从切片或者数组创建

** slice 有两个内置函数来获取其属性:**

len 获取 slice 的长度
cap 获取 slice 的容量

slice 的属性,这东西是什么,还需借助 unsafe 来探究一下。

package main import ( "fmt" "unsafe" ) func main() { s := make([]int, 10, 20) s[2] = 100 s[9] = 200 size := unsafe.Sizeof(0) fmt.Printf("%x\n", *(*uintptr)(unsafe.Pointer(&s))) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + size))) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + size*2))) fmt.Println(*(*[20]int)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&s))))) }

这段代码的输出如下 (Go Playground):

c00007ce90
10
20
[0 0 100 0 0 0 0 0 0 200 0 0 0 0 0 0 0 0 0 0]

这段输出除了第一个,剩余三个好像都能看出点什么, 10 不是创建 slice 的长度吗,20 不就是指定的容量吗, 最后这个看起来有点像 slice 里面的数据,但是数量貌似有点多,从第三个元素和第十个元素来看,正好是给 slice 索引 2 和 10 指定的值,但是切片不是长度是 10 个吗,难道这个是容量,容量刚好是 20个。

第二和第三个输出很好弄明白,就是 slice 的长度和容量, 最后一个其实是 slice 引用底层数组的数据,因为创建容量为 20,所以底层数组的长度就是 20,从这里了解到切片是引用底层数组上的一段数据,底层数组的长度就是 slice 的容量,由于数组长度不可变的特性,当 slice 的长度达到容量大小之后就需要考虑扩容,不是说数组长度不能变吗,那 slice 怎么实现扩容呢, 其实就是在内存上分配一个更大的数组,把当前数组上的内容拷贝到新的数组上, slice 来引用新的数组,这样就实现扩容了。

说了这么多,还是没有看出来 slice 是如何引用数组的,额…… 之前的程序还有一个输出没有搞懂是什么,难道这个就是底层数组的引用。

package main import ( "fmt" "unsafe" ) func main() { arr := [10]int{1, 2, 3} arr[7] = 100 arr[9] = 200 fmt.Println(arr) s1 := arr[:] s2 := arr[2:8] size := unsafe.Sizeof(0) fmt.Println("----------s1---------") fmt.Printf("%x\n", *(*uintptr)(unsafe.Pointer(&s1))) fmt.Printf("%x\n", uintptr(unsafe.Pointer(&arr[0]))) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s1)) + size))) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s1)) + size*2))) fmt.Println(s1) fmt.Println(*(*[10]int)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&s1))))) fmt.Println("----------s2---------") fmt.Printf("%x\n", *(*uintptr)(unsafe.Pointer(&s2))) fmt.Printf("%x\n", uintptr(unsafe.Pointer(&arr[0]))+size*2) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s2)) + size))) fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s2)) + size*2))) fmt.Println(s2) fmt.Println(*(*[8]int)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&s2))))) }

以上代码输出如下(Go Playground):

[1 2 3 0 0 0 0 100 0 200]
----------s1---------
c00001c0a0
c00001c0a0
10
10
[1 2 3 0 0 0 0 100 0 200]
[1 2 3 0 0 0 0 100 0 200]
----------s2---------
c00001c0b0
c00001c0b0
6
8
[3 0 0 0 0 100]
[3 0 0 0 0 100 0 200]

这段输出看起来有点小复杂,第一行输出就不用说了吧,这个是打印整个数组的数据。先分析一下 s1 变量的下面的输出吧,s1 := arr[:] 引用了整个数组,所以在第5、6行输出都是10,因为数组长度为10,所有 s1 的长度和容量都为10,那第3、4行输出是什么呢,他们怎么都一样呢,之前分析数组的时候 通过 uintptr(unsafe.Pointer(&arr[0])) 来获取数组起始位置的指针的,那么第4行打印的就是数组的指针,这么就了解了第三行输出的是上面了吧,就是数组起始位置的指针,所以 *(*uintptr)(unsafe.Pointer(&s1)) 获取的就是引用数组的指针,但是这个并不是数组起始位置的指针,而是 slice 引用数组元素的指针,为什么这么说呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgxzs.html