slice 本质就是一个sliceHeader 结构体
type sliceHeader struct {
ptr uintptr // 指向底层数组
len int
cap int
}
一、s := new ([] int) 执行之后
栈内存:
s ---------> 堆内存【sliceHeader】
ptr: nil
len: 0
cap: 0
执行 s := new([]int): 变量s存放在栈上,它保存的是堆中 sliceHeader 结构体的地址。堆上已经分配好了 sliceHeader,结构体内部字段全部置零:ptr=nil、len=0、cap=0,没有创建底层数组。*s 就是这个堆上的 sliceHeader,也就是 nil slice。
二、执行 *s = append(*s, 1)
append 发现 slice 的 ptr 是 nil,新建底层数组,然后修改堆上这个 sliceHeader:
栈内存:
s ---------> 堆内存【sliceHeader】
ptr: 指向新数组
len: 1
cap: 1
堆内存【底层数组】
[1]
执行 *s = append(*s, 1) 时,sliceHeader 仍然是原先 new 在堆上创建的那块内存,不会重新分配新的 sliceHeader。append 只会修改这个已有 header 内部的 ptr、len、cap 三个字段,同时分配一块全新的底层数组,让 header 的 ptr 指向这个新建数组,从而完成元素追加。
再追加 *s = append(*s,2):
栈内存:
s ---------> 堆内存【sliceHeader】
ptr: 指向新数组
len: 2
cap: 2
堆内存【底层数组】
[1, 2]
如果继续 append,len == cap,会重新分配更大的底层数组,拷贝旧元素,然后修改 header 的 ptr,sliceHeader 本身依然不变。
三、和make ([] int,3)区别
a := make([]int,3)
栈内存:
a [sliceHeader]
ptr: 底层数组
len:3
cap:3
堆内存【底层数组】
[0,0,0]
对于 a := make([]int,3): 变量 a 本身就是 sliceHeader,分配在栈上;make 内置函数会在堆上创建对应的底层数组,并且把 sliceHeader 里的 ptr、len、cap 填充好,ptr 指向这个刚创建好的底层数组。所以执行完 make,切片头和底层数组两者都准备好了,可以直接通过下标访问元素。
四、字面量 nums := []int{2,7,11,15}
栈内存:
nums [sliceHeader]
ptr:底层数组
len:4
cap:4
堆【底层数组】
[2,7,11,15]
五、对比 var t [] int
栈内存:
t [sliceHeader]
ptr:nil
len:0
cap:0
var t []int 声明出来的变量 t,本身就是栈上的 sliceHeader,内部 ptr 为 nil、len 和 cap 都是 0,属于 nil slice,没有任何堆内存分配。
而 s := new([]int),会在堆上分配一块内存存放 sliceHeader,这块内存清零后,内部同样是 ptr=nil、len=0、cap=0;*s 拿到的就是这个堆上的 sliceHeader。 所以 t 和 *s 的数据内容完全一致,都是 nil slice;唯一区别是 sliceHeader 的存放位置:t 的 header 在栈上,*s 的 header 在堆上。
六、为什么 new ([] int) ,结构体在堆上呢?
new(T) 的作用是在堆上分配一块大小对应类型 T 的内存,将内存置零后返回指向该堆内存的指针。所以调用 new([]int) 时,分配的是 []int 类型对应的 sliceHeader,这块 sliceHeader 内存位于堆上。