SliceHeader--slice的本质
问题:
slice空间容量足够,调用方法前后其地址并不会改变,那么为何append后的切片内部成员不会改变? 默认拷贝的副本是slice引用,应该要能修改或者添加成员才符合预期的
type Slice []int
func (A Slice)Append(value int) {
A = append(A, value)
}
func main() {
mSlice := make(Slice, 10, 20)
mSlice.Append(5)
fmt.Println(mSlice)
}
问题分析
以上的输出打印中,可以看到mSlice
并没有任何变化,就是方法Append
没有起任何作用。
append
后的Slice已经不是原来的Slice了。append
返回的Slice的指针和原Slice的指针一样的,怎么会不是一个呢?我们来测试一次,修改代码如下:
func (A Slice)Append(value int) {
A1 := append(A, value)
fmt.Printf("%p\n%p\n",A,A1)
}
>>>
0xc00009e000
0xc00009e000
A1
存储append
方法返回的Slice,然后打印返回A1
和原A
的指针地址,发现的确一样。在make
一个Slice的时候会发现,是可以有三个参数的,一个是数据、一个是长度、一个是容量,也就是说,Slice是这样的一个结构。
SliceHeader
SliceHeader是Slice运行时的具体表现,它的结构定义如下:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
对应Slice的三要素,Data
指向具体的底层数据源数组,Len
代表长度,Cap
代表容量。
Slice就是SliceHeader,把Slice转化为SliceHeader,来看看A
和A1
内部具体的字段值,判断是否一致,修改Append
方法如下:
func (A Slice)Append(value int) {
A1 := append(A, value)
sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)
sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}
通过unsafe.Pointer
指针进行强制类型转换,都转换为*reflect.SliceHeader
类型后,分别输出他们的Data
、Len
、Cap
字段,输出的结果:
A Data:824634368000,Len:10,Cap:20
A1 Data:824634368000,Len:11,Cap:20
Len
不一样,并不是一个Slice,所以使用append
方法并没有改变原来的A
,而是新生成了一个A1
,即使通过代码 A = append(A, value)
进行复制,也只是一个mSlice
的拷贝A
的指向被改变了,而且这个A
只在Append
方法内有效,mSlice
本身并没有改变,所以输出的mSlice
不会有任何变化。
解疑
如果设置的Len是10,Cap是20,因为Cap足够大,所以内置函数append
并没有生成新的底层数组,把Cap改为10。
type Slice []int
func (A Slice)Append(value int) {
A1 := append(A, value)
sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)
sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}
func main() {
mSlice := make(Slice, 10, 10)
mSlice.Append(5)
fmt.Println(mSlice)
}
发现两个Slice的Data
不再一样
A Data:824633794880,Len:10,Cap:10
A1 Data:824634302464,Len:11,Cap:20
在append
的时候,发现Cap
不够,生成了一个新的Data
数组,用于存储新的数据,并且同时扩充了Cap
容量。