我们知道,Go中的slice是一个引用类型的值。 那么,当我们把slice当成一个函数参数传递之后,如果在函数中修改了该参数的值,会不会影响原来的slice呢?
一句话结论:转自:https://juejin.im/post/5eeb2f436fb9a0586320a63e?
go语言中文文档:www.topgoer.com
Go的slice类型中包含了一个array指针以及len和cap两个int类型的成员。
Go中的参数传递实际都是值传递,将slice作为参数传递时,函数中会创建一个slice参数的副本,这个副本同样也包含array,len,cap这三个成员。
副本中的array指针与原slice指向同一个地址,所以当修改副本slice的元素时,原slice的元素值也会被修改。但是如果修改的是副本slice的len和cap时,原slice的len和cap仍保持不变。
如果在操作副本时由于扩容操作导致重新分配了副本slice的array内存地址,那么之后对副本slice的操作则完全无法影响到原slice,包括slice中的元素。
下面,我们就通过几个例子来实际感受一下。
场景一:在函数中修改slice的成员的值可以看到: 函数中修改了weight值之后,原来的myWeight序列也同样被修改了。
比较几次打印的地址我们可以看到:
原myWeight变量的地址是0xc000058400,它是一个slice,引用类型,指向的地址实际是0xc000090040。
而slice myWeight在作为函数参数传递时,实际上传递的是引用指向的地址0xc000090040,函数实际上另外开辟了一个临时变量weight来存放这个引用的值,新变量的地址是0xc000058480。
我们在resetWeight函数中修改slice weight中的值时,实际上修改的是weight指向的地址0xc000090040存放的内容。 而此时,函数外部的myWeight指向的地址同样是0xc000090040,因此,我们就看到slice myWeight的内容被修改了。
场景二:在函数中向slice添加成员这一次我们发现,在函数中修改slice weight中变量值后,外部的myWeight能获取到这个修改;在函数中向slice weight添加元素时,外部的myWeight却并没有随之增加元素。
这又是为什么呢?
我们来看一下go源码中对slice的定义:
go/src/runtime/slice.go文件
slice中其实包含了三个成员: 一个指针类型的array,一个int类型的len,以及一个int类型的cap。
在slice作为参数传递时,实际上是将原来的slice做了一个拷贝,函数中新的slice变量拿到的其实是一个指针类型和两个int类型的值。
当我们在函数中修改slice时,如果修改的是指针,原slice的array同样指向这个地址,就可以感知到这个修改。但如果修改的是int变量,原slice就无法感知到。
所以,我们这里在函数中把weight第一个变量改为10之后,myWeight中的第一个变量值也变成了10,因为这两个slice的array都是指向的同一个内存地址。
当我们向weight中添加元素时,weight的len会变大,但是myWeight的len却不会发生改变,它的长度仍然为1,只能读到第一个元素10。
场景三:在函数中向slice添加成员,并且超过了原来的cap容量我们修改一下刚刚的addWeightRecord()方法:
这一组函数在对slice weight添加元素时,超过了weight的容量。这时候会为weight重新分配更大的内存,来存放更多的元素。
我们可以看到,这时候weight本身的地址并没有变化,仍然是0xc000058480,但是array指向的内存地址却发生了变化,从0xc0000600a0变成了0xc000074060。
在这一次运行中,只有在weight还没扩容时修改第一个元素为10的操作被原slice感知到了,其他操作对原来的slice都没有影响。