运行一下查看结果
$ go run slice.go
slice memory: [0 64 6 32 200 0 0 0 10 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0]
slice struct: &{0xc820064000 10 20}
ptr:0xc820064000 len:10 cap:20
golang slice len:10 cap:20
array values: [0 1 2]
Slice len: 15
golang slice len: 15
看到了,golang slice 的memory内容,和自定义的 Slice 的值,还有按照 slice 中的指针指向的内存,就是实际 Array 数据。当修改了 slice 中的len, len(s) 也变了。
接下来结合几个例子,了解下slice一些用法
声明一个Array通常使用 make ,可以传入2个参数,也可传入3个参数,第一个是数据类型,第二个是 len ,第三个是 cap 。如果不穿入第三个参数,则 cap=len ,append 可以用来向数组末尾追加数据。
这是一个 append 的测试
// 每次cap改变,指向array的ptr就会变化一次
s := make([]int, 1)
fmt.Printf("len:%d cap: %d array ptr: %v n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
for i := 0; i < 5; i++ {
s = append(s, i)
fmt.Printf("len:%d cap: %d array ptr: %v n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
}
fmt.Println("Array:", s)
运行结果
len:1 cap: 1 array ptr: 0xc8200640f0 len:2 cap: 2 array ptr: 0xc820064110 len:3 cap: 4 array ptr: 0xc8200680c0 len:4 cap: 4 array ptr: 0xc8200680c0 len:5 cap: 8 array ptr: 0xc82006c080 len:6 cap: 8 array ptr: 0xc82006c080 Array: [0 0 1 2 3 4]
看出来了吧,每次cap改变的时候指向array内存的指针都在变化。当在使用 append 的时候,如果 cap==len 了这个时候就会新开辟一块更大内存,然后把之前的数据复制过去。
实际go在append的时候放大cap是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap 。所以上面的测试中cap变化是 1, 2, 4, 8
在实际使用中,我们最好事先预期好一个cap,这样在使用append的时候可以避免反复重新分配内存复制之前的数据,减少不必要的性能消耗。
创建切片
s := []int{1, 2, 3, 4, 5}
fmt.Printf("len:%d cap: %d array ptr: %v n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
fmt.Println("Array:", s)
s1 := s[1:3]
fmt.Printf("len:%d cap: %d array ptr: %v n", len(s1), cap(s1), *(*unsafe.Pointer)(unsafe.Pointer(&s1)))
fmt.Println("Array", s1)
运行结果
len:5 cap: 5 array ptr: 0xc820012210 Array: [1 2 3 4 5] len:2 cap: 4 array ptr: 0xc820012218 Array [2 3]
在一个切片基础上创建新的切片 s1 ,新切片的 ptr 指向的就是 s1[0] 数据的内存地址。可以看到指针地址 0xc820012210 与 0xc820012218 相差 8byte 正好是一个int类型长度,cap也相应的变为4
就写到这里了,总结一下,切片的结构是指向数据的指针,长度和容量。复制切片,或者在切片上创建新切片,切片中的指针都指向相同的数据内存区域。










