go 切片在进行子函数参数传递时的一些探究发现

go 切片在进行子函数参数传递时的一些探究发现

最近在复习go知识点的时候,回顾到指针类型,还是值类型的知识点,关于这个知识点,大家可以看一下我po出来的这个链接:https://keer.me/values-or-pointers-in-golang.html。对指针类型和值类型在使用上的区别做了很详细的阐述。

我们都知道,go语言在函数参数的传递时是值传递。而切片在进行参数传递时,也同样是一种值传递的形式。但切片本身是一种引用类型,因此在值传递时,拷贝的是切片的引用地址(切片在底层维护一个数组,通过确定上下界的方式来标定切片)。

所以,拷贝的切片地址实际上还是引用的原数组,对于子函数中的切片修改实际上会影响到原数组,看下面的示例:

func main() {
   arr := []int{3, 1, 4, 5}
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
   solve(arr)
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
}

func solve(arr []int) {
   arr[0] = 100
}

// output
arr: [3 1 4 5], arr的地址为: 0xc000118000
arr: [100 1 4 5], arr的地址为: 0xc000118000

可以看出来,经过solve函数(子函数)处理后的arr发生了变化,并体现在主函数(父函数)中,即主函数中的arr变化了相应的值。

那在子函数中对切片参数进行增加会怎么样呢?让我们来试一试

func main() {
   arr := []int{3, 1, 4, 5}
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
   solve(arr)
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
}

func solve(arr []int) {
   arr = append(arr, 51)
   fmt.Printf("solve函数中的arr: %v, arr的地址为: %p\n", arr, &arr)
}

// output
arr: [3 1 4 5], arr的地址为: 0xc00000c030
solve函数中的arr: [3 1 4 5 51], arr的地址为: 0xc00000c060
arr: [3 1 4 5], arr的地址为: 0xc00000c030

在solve函数中,我们对arr进行了append操作,添加了一个51。这时我们可以发现:主函数中的arr并没有发生变化,而solve函数中的arr添加成功了。这似乎与第一个代码段,对arr进行修改的操作有所出入:为什么我对切片的修改并没有引起引用数组的变化呢?

让我们来分析一下,第二个代码段的solve函数做了什么事情?append操作。那第一个代码段的solve做了什么事情?对传过来arr切片进行修改。那可以很容易地知道,append操作对原本的内存布局进行了改变。那进行了什么样的改变呢?

我们都知道,在对切片进行初始化时,除了需要声明切片的类型之外,还有两个变量:cap(容量)和len(长度)

make([]T, length, capacity)

而上述代码块2中的arr在声明时,其实隐含了arr的len为4,cap也为4:

func main() {
   arr := []int{3, 1, 4, 5}
   fmt.Printf("len(arr): %v, cap(arr): %v\n", len(arr), cap(arr))
}

// output
len(arr): 4, cap(arr): 4

而在solve函数中,对arr切片进行了append操作,此时arr的容量(cap)其实满足不了新元素的添加,因此对append操作后的arr进行了扩容操作。而一旦扩容操作突破了原有的cap,go便会在内存中申请新的数组,并返回对新数组的引用。solve函数中的arr引用的数组与主函数中引用的数组并不是同一个,所以出现了append操作后,arr不一致的情况。

那我提前将arr切片的cap改大可以可以呢?看下面的代码:

func main() {
   arr := make([]int, 4, 6)
   arr[0], arr[1], arr[2], arr[3] = 3, 1, 4, 5
   fmt.Printf("len(arr): %v, cap(arr): %v\n", len(arr), cap(arr))
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
   solve(arr)
   fmt.Printf("arr: %v, arr的地址为: %p\n", arr, &arr)
}

func solve(arr []int) {
   arr = append(arr, 51)
   fmt.Printf("len(arr): %v, cap(arr): %v\n", len(arr), cap(arr))
   fmt.Printf("solve函数中的arr: %v, arr的地址为: %p\n", arr, &arr)
}

// output
len(arr): 4, cap(arr): 6
arr: [3 1 4 5], arr的地址为: 0xc000118000
len(arr): 5, cap(arr): 6
solve函数中的arr: [3 1 4 5 51], arr的地址为: 0xc000118030
arr: [3 1 4 5], arr的地址为: 0xc000118000

可以发现,主函数中的arr依旧没有发生变化。按照我们上述的分析,主函数的arr是会发生改变的,然而现实又给了我一巴掌。这又是为什么呢?

仔细想一下可以发现,solve函数确实对底层数组进行了修改,但是主函数arr对底层数组的引用方式其实一直都没有发生变化。假设底层数组是ARR,主函数的arr其实一直引用的都是ARR[0:4],solve函数并没有对主函数中arr的引用方式进行修改。所以不难理解为什么主函数中的arr并没有发生变化了。

结论

在父子函数的参数传递中,子函数对切片的修改是会对父函数的切片产生影响的,而子函数对切片的增加是不会对父函数的切片产生影响的。

参考:

https://blog.csdn.net/qq_21154829/article/details/121079752

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值