为何Slice作为函数参数时,函数内对Slice的修改不会反映在原Slice上?
2023-12-20 12:25:52
slice 作为函数参数时的值传递和引用传递
前言
在 Go 语言中,slice 是一个动态数组,它允许我们存储一组同类型的值。当我们将 slice 作为函数参数传递时,函数内对 slice 的修改通常不会反映在原 slice 上。这是因为 slice 是引用类型,函数参数传递的是 slice 的副本,而不是原 slice 本身。
slice 的底层实现
为了理解其中的原因,我们需要了解 slice 的底层实现原理。slice 实际上是一个包含三个字段的结构体:
- len: slice 的长度,表示其中包含的元素数量。
- cap: slice 的容量,表示 slice 可以容纳的最大元素数量。
- array: 指向存储 slice 元素的底层数组的首地址。
值传递 vs. 引用传递
在 Go 语言中,函数参数传递有两种方式:值传递和引用传递。值传递传递的是变量的副本,而引用传递传递的是变量的地址。
当我们将 slice 作为函数参数传递时,函数参数实际上得到的是 slice 的副本。这意味着函数内对 slice 的修改只会影响副本,而不会影响原 slice。这是因为 slice 的 copy 会创建一个新的数据结构,其中包含自己的长度、容量和指向底层数组的新指针。
示例:值传递
以下示例演示了 slice 值传递的工作原理:
package main
import "fmt"
func main() {
mySlice := []int{1, 2, 3}
modifySlice(mySlice)
fmt.Println(mySlice) // 输出:[1, 2, 3]
}
func modifySlice(s []int) {
s[0] = 4
}
在这个示例中,当我们调用 modifySlice 函数时,函数参数 s 得到的是 mySlice 的副本。然后,我们在函数内将 s 的第一个元素修改为 4。但是,这个修改只影响 s 指向的内存空间,而不会影响 mySlice 指向的内存空间。因此,当我们在 main 函数中打印 mySlice 时,输出结果仍然是 [1, 2, 3]。
引用传递:使用指针
为了让函数内对 slice 的修改反映在原 slice 上,我们需要使用指针来传递 slice。指针是指向一块内存地址的变量,当我们把 slice 的指针作为函数参数传递时,函数参数实际上得到的是 slice 的原地址。这样,函数内对 slice 的修改就会直接反映在原 slice 上。
示例:引用传递
以下示例演示了如何使用指针进行引用传递:
package main
import "fmt"
func main() {
mySlice := []int{1, 2, 3}
modifySlice(&mySlice)
fmt.Println(mySlice) // 输出:[4, 2, 3]
}
func modifySlice(s *[]int) {
(*s)[0] = 4
}
在这个示例中,当我们调用 modifySlice 函数时,函数参数 s 得到的是 mySlice 的原地址。然后,我们在函数内将 *s 的第一个元素修改为 4。由于 *s 指向的是 mySlice 的原地址,因此这个修改会直接反映在 mySlice 上。当我们在 main 函数中打印 mySlice 时,输出结果为 [4, 2, 3]。
需要注意的是 ,使用指针来传递 slice 可能会导致一些潜在的问题,例如并发访问冲突。因此,在使用指针传递 slice 时,需要仔细考虑是否会带来潜在的风险。
常见问题解答
1. 为什么 slice 作为函数参数传递时不会修改原 slice?
因为 slice 是引用类型,函数参数传递的是 slice 的副本,而不是原 slice 本身。
2. 如何让函数内对 slice 的修改反映在原 slice 上?
使用指针来传递 slice,指针指向原 slice 的内存地址。
3. 值传递和引用传递有什么区别?
值传递传递变量的副本,而引用传递传递变量的地址。
4. 使用指针传递 slice 有什么潜在的风险?
并发访问冲突。
5. 何时应该使用值传递,何时应该使用引用传递?
当不需要函数内对变量的修改影响到原变量时使用值传递。当需要函数内对变量的修改反映在原变量上时使用引用传递。