Go 语言的参数传递

简介: 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)
  }


运行后我电脑上的结果,

1668503014982.jpg

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

1668503028521.jpg


传 *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"
}


运行结果,

1668503060658.jpg

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

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

1668503073528.jpg

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

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

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

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

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


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


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

  • 如果不想传递的变量被修改,那么就传 T。这样我们无需关心调用的函数对变量做了什么修改操作。
  • 大的结构体(struct) 或者数组。比如我们通过调用外部接口获取某些数据解析到对应的 struct 后,然后层层的把这个 struct 以 T 形式传入下游业务服务,那么会导致这个 T 频繁的创建副本,影响性能。这个时候可以考虑传递 *T,但是需要考虑是否会对业务的正确性造成破坏。
  • 对于函数作用域内的参数,如果定义成 T,Go 编译器会尽可能将对象分配到栈上,如果是 *T ,因为指针的存在,对象可能不能随着函数的结束而结束,进而导致对象被分配到堆上,对GC多少会产生影响。
  • ......


相关阅读


[1]https://golang.org/doc/faq#pass_by_value[2]https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html[3]https://colobu.com/2017/01/05/-T-or-T-it-s-a-question/

相关文章
|
5天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
3天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
11 2
|
3天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
14 2
|
7天前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
84 58
|
6天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
3天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
10 4
|
3天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
14 1
|
7天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
7天前
|
Go 数据处理 调度
探索Go语言的并发模型:Goroutines与Channels的协同工作
在现代编程语言中,Go语言以其独特的并发模型脱颖而出。本文将深入探讨Go语言中的Goroutines和Channels,这两种机制如何协同工作以实现高效的并发处理。我们将通过实际代码示例,展示如何在Go程序中创建和管理Goroutines,以及如何使用Channels进行Goroutines之间的通信。此外,本文还将讨论在使用这些并发工具时可能遇到的常见问题及其解决方案,旨在为Go语言开发者提供一个全面的并发编程指南。
|
5天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。