当前位置: 首页 > news >正文

golang函数传参——值传递理解

做了五年的go开发,却并没有什么成长,都停留在了业务层面了。一直以为golang中函数传参,如果传的是引用类型,则是以引用传递,造成这样的误解,实在也不能怪我。我们来看一个例子,众所周知,slice是个引用类型,我们以slice为例。

package main
​
import "fmt"
​
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下没有在函数内部修改的初始情况fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函数内部修改初始slice内容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函数传参地址:",&str[0])str[0]="改掉这个内容"
}

猜猜,打印出来的会是什么结果?

 

​不管你是怎么看,如果只有这么一个案例,就很容易产生误解。打印的函数参数的地址和外部slice的地址是一致,并且在函数体内修改的值的确影响了slice的值,由此现象很容易得出是引用传递

事实果真如此么?我们再来看一个案例。

package main
​
import "fmt"
​
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下没有在函数内部修改的初始情况fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函数内部修改初始slice内容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函数传参地址:",&str[0])str=append(str,"新增一个内容")
}

 如果是引用传递,那么经过change函数追加了值的strSlice应该是能打印出追加的值。好了,我们直接看结果。

结果,很是出乎我的意料之外啊,竟然不是像我上面猜想的那样。由此至少可以得出一个结论,golang中函数的参数传递不是引用传递。那么,也就是说即使参数是引用类型,也是值传递,既然是值传递,第一个案例作何理解呢?第二个案例又如何理解呢?

我们看一下slice的底层结构。

//go 1.20.3 path: /src/runtime/slice.go
type slice struct {array unsafe.Pointerlen   intcap   int
}

 array 是一个指向底层数组的指针,这个数组存储着切片中的元素。len 表示切片的长度,即切片中元素的数量。cap 表示切片的容量,即切片底层数组中可用的元素数量。golang的函数传参都是值传递,即使传递的是引用类型,也是对应引用类型的地址拷贝。因此,第一个案例中,实际上是把指向底层数组的指针的地址拷贝生成一个副本传到了函数体中,所以,第一个案例中修改了0xc00006c0a0地址里的内容会引发外面的参数发生变化。这个我们可以做个案例测试一下。

package main
​
import "fmt"
​
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下没有在函数内部修改的初始情况fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函数内部修改初始slice内容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函数传参地址:",&str[0])for i:=0;i<10;i++{str = append(str, fmt.Sprint(i))}fmt.Println("扩容之后的地址:",&str[0])str[0]="改掉这个内容"
}

我们知道,当slice发生扩容,runtime会开辟一块新的内存地址把内容拷贝到新的地址指向的内存中,那么,我们可以测试一下,当slice发生扩容,再修改内容,就不会影响原来的参数。

 

​实际结果,验证了我们的猜想,扩容之后,开辟新的内存地址来存放内容,因此,再修改这个参数也不会影响外部参数。

可是这个依然没有解除掉第二个案例——没有扩容时,函数内append之后外部参数打印结果和预期不符的疑惑。实际上并不矛盾,因为,slice结构中有一个变量len,这个表示slice中元素的数量,用大白话来理解就是可见的元素,传参的过程中,不仅拷贝了地址,还拷贝了len和cap,因此,虽然形参中的len发生了变化,但是并不影响实参的len。画个内存示意图来理解一下。

package main
​
import "fmt"
​
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下没有在函数内部修改的初始情况fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0],"len=",len(strSlice))//在函数内部修改初始slice内容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0],"len=",len(strSlice))
}
func change(str []string){fmt.Println("函数传参地址:",&str[0])str=append(str,"新增一个内容")fmt.Println("形参str长度:",len(str))
}

 

直接看结果,果然验证了我们上面的猜想。

至此,函数值传递的探究到此结束。

 

http://www.lryc.cn/news/111097.html

相关文章:

  • Liunx环境下git的详细使用(gitee版)
  • exoplayer的使用-2,与flutter相互通信
  • 【基础类】—类型转换
  • 【云原生】 一文了解docker中的网络
  • 嵌入式开发学习(STC51-15-红外遥控)
  • 代码编辑器实践之vue-codemirror使用
  • Mapstruct
  • 初阶C语言——特别详细地介绍函数
  • pulsar-client-1-2 PulsarClient构造函数
  • 原型链污染是什么
  • java之石头迷阵单击游戏、继承、接口、窗体、事件、组件、按钮、图片
  • Debian 系列 Linux 的静态 DNS 、gateway 、IP 设置和网络重启
  • IP路由基础+OSPF 基础
  • window远程连接Linux
  • MyBatis 查询数据库之二(增、删、改、查操作)
  • unraid docker桥接模式打不开页面,主机模式正常
  • 第七章:进程间通信(IPC)——构成进程间通信的信道方案
  • 部分常用CSS样式
  • 思科单臂路由、lacp链路聚合、NAT实验
  • 【力扣每日一题】2023.8.5 合并两个有序链表
  • QT 驱动条码打印机(没有验证过)
  • Kafka介绍
  • Django使用uwsgi+nginx部署,admin没有样式解决办法
  • 穷举深搜暴搜回溯剪枝(3)
  • Bash 脚本的参数等
  • 从哪些方面学HTML技术? - 易智编译EaseEditing
  • 非阻塞IO
  • Debian如何让multilib和交叉编译工具链共存
  • Flink之JDBC Sink
  • lifecycleScope Unresolved reference