• 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
    %p
    ",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
    ",sh.Data,sh.Len,sh.Cap)
    
    	sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
    	fmt.Printf("A1 Data:%d,Len:%d,Cap:%d
    ",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
    ",sh.Data,sh.Len,sh.Cap)
    
    	sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
    	fmt.Printf("A1 Data:%d,Len:%d,Cap:%d
    ",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容量。

  • 相关阅读:
    wget: command not found
    小程序循环多个picker选择器,实现动态增、减
    小程序 picker 多列选择器 数据动态获取
    有关https有的网站可以访问有的访问不了的问题
    微信小程序填坑之路
    linux如何搭建sftp服务器
    微信小程序模板中使用循环
    C#学习笔记(20)——使用IComparer(自己写的)
    C#学习笔记(19)——使用IComparer(百度文库)
    C#学习笔记(18)——C#构造函数中this和base的使用
  • 原文地址:https://www.cnblogs.com/remixnameless/p/14579569.html
Copyright © 2020-2023  润新知