开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
golang中的slice到底是引用传递还是值传递?
很多新学习golnag语言的伙伴可能都会遇到这个问题。为什么有时候自己的slice切片进行修改时不需要加指针即可完成修改操作。但有的时候slice切片修改之后结果却没有发生改变。这时候我们就会思考一个重要的问题:golang中的slice到底是引用传递还是值传递呢?
先简要叙述一下什么是值传递什么是引用传递
值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身。
对结果进行回答,引出思考
golang中没有引用传递这个方式,golang中的所有类型都只进行值传递。有的只是引用类型。(参考go官方文档)这时候有的人就要拿出例子了,我偏不相信,我操作slice进行修改的时候明明结果发生了改变,为什么不是引用传递呢? 直接进行第一个示例分析:
func main() {
a := []int{1, 2, 3, 4, 5, 6}
fmt.Println(a)
change(a)
fmt.Println(a)
}
func change(a []int) {
for i, val := range a {
if i%2 == 0 {
a[i] = val + 1
}
}
}
运行结果:
[1 2 3 4 5 6]
[2 2 4 4 6 6]
这里可以看到,change函数对切片进行了改变,并且该影响成功了。这时有人就要说你看吧,golang中的slice就是引用传递。那看下一个例子
func main() {
a := []int{1, 2, 3, 4, 5, 6}
fmt.Printf("原始a切片为: %v, 地址p为: %p\n", a, &a)
change(a)
fmt.Printf("调用change函数后切片为: %v, 地址p为: %p\n", a, &a)
}
func change(a []int) {
for i, val := range a {
a[i] = val + 1
}
fmt.Printf("add函数内部的切片为: %v, 地址p为: %p\n", a, &a)
}
运行结果:
原始a切片为: [1 2 3 4 5 6], 地址p为: 0xc000008078
add函数内部的切片为: [2 3 4 5 6 7], 地址p为: 0xc0000080a8
调用change函数后切片为: [2 3 4 5 6 7], 地址p为: 0xc000008078
这个时候就会发现,切片a的地址神奇般的改变了!!!但是a的结果仍然受到了函数影响,发生了改变,那么这到底是为什么呢?既然切片不是一个引用传递,为什么a的结果还能发生改变呢?
原因解释:
当初发现这个问题的时候,我也是抱着疑惑去搜索了一番,发现结果的我恍然大悟。来先看一看切片的结构体组成,在进行问题分析.
type slice struct {
array unsafe.Pointer
len int
cap int
}
type Pointer *ArbitraryType
切片是由指针数组、切片长度、切片容量这三个变量组成的。切片长度表示的是切片的存储数据的长度。切片容量则表示当切片需要进行扩容时,会按照容量乘以一个数值进行扩大(大概是乘以1.5)。并且将这个扩容后的切片进行返回。重点来了:这个指针数组是干嘛用的呢,其实这就是为什么切片通过值传递发生改变后依旧能影响到原切片的原因。切片中的每个元素都对应着一个固定的地址。也就是说切片内部的指针指向同一个数据源。所以无论切片如何发生改变,只要数据源不变,切片里边的值就会随着数据源的改变而改变。
golang中的四大基本类型
- 基本类型 包括:数字、字符型、布尔型.
- 复合类型 包括: 数组、结构体.
- 引用类型 包括: pointer(指针)、complex(复数)、slice(切片)、func(函数)、map(字典)、channel(管道)、
- 接口类型 包括: interface(接口)等对其他类型行为的抽象和概括
小技巧
使用append可以对切片进行初始化,而无需make
省流总结
- golang中没有引用传递,只有值传递.
- golang中的"引用传递"实现效果实际上是通过,数据内部的数据源来提供的.
- golang中所有的参数传递进函数中进行操作都是拷贝参数值进行操作的.