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,来看看AA1内部具体的字段值,判断是否一致,修改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类型后,分别输出他们的DataLenCap字段,输出的结果:

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容量。

posted @ 2021-03-25 22:09  自己有自己的调调、  阅读(414)  评论(0编辑  收藏  举报