slice

// slice 数据结构
type slice struct {
	array unsafe.Pointer 
	len   int            
	cap   int            
}
func main() {
	array := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Printf("%p\n", &array) // 0xc00011c000
	s1 := array[:5]
	s2 := array[3:]
	fmt.Printf("%p s1 =%v\n", s1, s1)  // 0xc00011c000 [1 2 3 4 5]
	fmt.Printf("%p s2 =%+v\n", s2, s2) // 0xc00011c018 [4 5 6 7 8 9 0]
	s2[0] = 0
	fmt.Printf("%p s1 =%v\n", s1, s1)           // 0xc00011c000 [1 2 3 0 5]
	fmt.Printf("%p s2 =%v\n", s2, s2)           // 0xc00011c018 [0 5 6 7 8 9 0]
	fmt.Printf("%p array =%v\n", &array, array) // 0xc00011c000 [1 2 3 0 5 6 7 8 9 0]
}

可以看到s1 := array[:5]与原数组的地址相同,s2 := array[3:]与原数组地址不同,但修改s2时,同时修改到了s1和原数组,因为这3者的底层数组是同一个,即原array,s2地址不同的原因是因为它是从下标为3开始的。

扩容

func appendSli(sli []int) {
  // 0xc000090000 0xc00000c060 [2]
	fmt.Printf("slice address in appendSli1:%p %p %+v\n", sli, &sli, sli) 
	sli[0] = 4
	sli = append(sli, 3)
  // 0xc000090030 0xc00000c060 [4 3]
  // sli发生了扩容->sli有了新的底层数组->%p sli的地址变了
  // 但此时&sli并没有变化, 因为sli变量还是不变的(slice是个struct), 变的是底层数组
	fmt.Printf("slice address in appendSli1:%p %p %+v\n", sli, &sli, sli)  
  // 这里再次修改sli的值, 已经不能影响到外部sli变量了
  // 因为此时本函数内部的sli变量对应的底层数组和main的sli变量对应的底层数组已经不是同一个数组了
	sli[0] = 5 
}
func main() {
	s := make([]int, 0, 1)
	s = append(s, 1)
	fmt.Printf("%p %p %+v\n", s, &s, s)  // 0xc000090000 0xc00008e000 [1]
	s[0] = 2
	appendSli(s)
	fmt.Printf("%p %p %+v\n", s, &s, s)  // 0xc000090000 0xc00008e000 [4]
}

fmt特殊处理了slice类型, 以%p打印slice其实打印的是该slice变量对应的底层数组的首元素的地址

%p打印&slice打印的是该slice(结构体)变量的地址

在调用函数时,会开辟一个新的内存空间做值复制,由于这个struct有array这个指针, 所以slice副本也指向了同一个array,所以也可以做到修改外部变量的值。

但是这个副本的len和cap在函数内部改动之后,外部变量是不变的, 因为它本身是个新的slice变量, len和cap字段是独立的。

可以通过函数修改存储元素的内容,但是永远修改不了lencap,因为他们只是一个拷贝,如果要修改,那就要传递*slice作为参数才可以。

slice会在容量cap不足时扩容,开辟新的array空间,把旧的复制过去。

nil vs empty slice

// nil slice
var slice []string
slice == nil // true

// Empty slice
var slice = []string{}
// or
var slice = make([]string, 0)
slice == nil // false

nil 切片是在未初始化的情况下声明的切片。它的长度和容量为 0,没有底层数组,数组的零值为 nil。

空切片是包含零个元素的切片。它有底层数组,但元素个数为零。

在大多数情况下,nil 切片和空切片之间没有明显的区别。内置函数 append、len 和 cap 对两者都返回相同的结果,并且可以使用 for...range(0 次迭代)。

但是,在json序列化时有区别,nil slice 会被序列化为 null,而 empty slice 被序列化为[]

Last updated

Was this helpful?