打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Go 语言的参数传递

前言

对于一门编程语言,在我们调用一个函数并且传递参数的时候,可能会下意识的去思考,到底是按值传递(by value) 还是按引用(by reference) 传递。

首先,在 Go 的 faq 中明确表示过所有东西都是按值传递的[1] ,并不存在引用传递。

As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter.

但是我们在项目中经常会看到这样的代码,一会传 T,一会传 *T,传 *T 为啥就不是引用传递

func changeString1(name string) {}
func changeString2(name *string) {}

传 T

我们先从传 T 的代码开始看,

package main
import 'fmt'
type user struct { Name string}
func main() { var u user  u.Name = 'wuqq' fmt.Printf('u的值:%+v,内存地址:%p\n', u, &u) ChangeUser(u) fmt.Printf('调用函数后的值:%+v,内存地址:%p\n', u, &u)}
func ChangeUser(userInfo user) { fmt.Printf('接收到u的值:%+v,内存地址:%p\n', userInfo, &userInfo) userInfo.Name = 'curry' fmt.Printf('修改后u的值:%+v,内存地址:%p\n', userInfo, &userInfo)  }

运行后我电脑上的结果,

可以看到,传递的参数 u (内存地址0xc000010200) 会创建一个副本(内存地址0xc000010220) 到函数 ChangeUser 中,在函数中对参数的修改不会影响到原始的值,因为此时,本质上是这样的。

传 *T

我们修改下上面的示例,修改成传 *T,

package main
import 'fmt'

type user struct {  Name string}
func main() {  u := &user{Name: 'wuqq'}  fmt.Printf('u的值:%+v,内存地址:%p,指针地址:%p\n', *u, u, &u)  ChangeUser(u)  fmt.Printf('调用函数后的值:%+v,内存地址:%p,指针地址:%p\n', *u, u, &u)}func ChangeUser(userInfo *user) {  fmt.Printf('接收到u的值:%+v,内存地址:%p,指针地址:%p\n', *userInfo, userInfo, &userInfo)  userInfo.Name = 'curry'}

运行结果,

首先,我们需要知道,任何存放在内存中的东西都有自己的地址。指针也一样,指针虽然指向的是别的数据,但是指针的本身也是需要内存空间进行存储的。

上面 u 指针它的内存地址是 0xc00000e028。当我们把 u 指针传递给函数时,会创建一个指针的副本(地址:0xc00000e038),只不过指针 0xc00000e028 和指针 0xc00000e038 都指向了同一个内存地址 0xc000010200。那么此时对 *user 的修改势必会影响到原始传入的值。因为,本质上他们指向的是同一个对象。


从上面可以看出,当一个变量被当作参数传递的时候,一定会创建这个变量的副本,即按值传递。

如果传递的是 T,创建的是参数的整个副本。

如果传递的是 *T,创建的是指针的副本。

虽然两个指针所存储的内存地址不一样,但是它们的值是相同的,指向了相同的内存地址。比如上面的 0xc00000e028 和 0xc00000e038 两个指针的内存地址不一样,但是都指向了 0xc000010200 这个内存

因此就可以解释无论在 Go 中传递的是什么类型,本质上都是值传递。只是有时候拷贝的是非引用的类型,比如 int,string,struct...... ,这样无法在调用函数中修改原对象数据,

 什么时候传 T,什么时候传 *T

更多的是看副本创建所需的成本和自己的需求。

  • 如果不想传递的变量被修改,那么就传 T。这样我们无需关心调用的函数对变量做了什么修改操作。

  • 大的结构体(struct) 或者数组。比如我们通过调用外部接口获取某些数据解析到对应的 struct 后,然后层层的把这个 struct 以 T 形式传入下游业务服务,那么会导致这个 T 频繁的创建副本,影响性能。这个时候可以考虑传递 *T,但是需要考虑是否会对业务的正确性造成破坏。

  • 对于函数作用域内的参数,如果定义成 T,Go 编译器会尽可能将对象分配到栈上,如果是 *T ,因为指针的存在,对象可能不能随着函数的结束而结束,进而导致对象被分配到堆上,对GC多少会产生影响。

  • ......

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Go 语言系列13:指针
漫谈golang设计模式 简易工厂模式
一篇文章带你了解Go语言基础之指针
Go语言切片一网打尽,别和Java语法傻傻分不清
Golang
uboot启动后在内存中运行裸机程序hello
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服