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

没错,Go 语言的函数参数没有引用传递方式

这篇文章想浅浅地讲解 Go 语言函数参数传递的值拷贝。

一句话观点

Go语言中所有传递都是值传递,严格来说并不存在引用传递的概念。传递指针只是传递指针的值,并不是引用传递,只不过通过指针可以间接修改变量的值,从而达到类似引用传递的效果。

值传递

值传递就是将参数的副本传递给函数,因此在函数内部修改参数的值,不会影响到原始变量的值。

  
func modifyValue(person Person) {  
    person.Name = "Alice"  
}

func Test4(t *testing.T) {  
    person1 := Person{Name: "Bob"}  
    modifyValue(person1)  
    fmt.Println("值传递:", person1.Name) // 输出: 值传递: Bob
}

在这个例子中,modifyValue 函数接收 person 的副本,修改它的字段值并不会影响原来的 person

引用传递

要实现引用传递,可以通过传递指针来实现。

func modifyReference(person *Person) {  
    person.Name = "Alice"  
}

func Test4(t *testing.T) {  
	person2 := &Person{Name: "Bob"}  
	modifyReference(person2)  
	fmt.Println("引用传递:", person2.Name) // 输出: 引用传递: Alice
}

在这个例子中,modifyReference 函数接收的是 person 指针类型的副本,修改副本指向的值,会影响到原来的。

值传递例子

下面再看一个值传递的例子。恰好最近在读《Go 语言精进之路》这本书,书里有一段讲解的方法的本质问题,案例是下面这段代码:

type field struct{ name string }  
  
func (p *field) print() {  
    fmt.Println(p.name)  
}  
func main() {  
    data1 := []*field{{"one"}, {"two"}, {"three"}}  
    for _, v := range data1 {  
       go v.print()  
    }  
    data2 := []field{{"four"}, {"five"}, {"six"}}  
    for _, v := range data2 {  
       go v.print()  
    }  
    time.Sleep(3 * time.Second)  
}

我对上面的代码进行简单改造,改为单测方法,打印输出内存地址,改造的代码如下:

  
func (p *field) print() {  
    fmt.Printf("%p %s \n", p, p.name)  
}  
  
func Test1(t *testing.T) {  
    data1 := []*field{{"one"}, {"two"}, {"three"}}  
    for _, v := range data1 {  
       go (*field).print(v)  
    }  
    
    data2 := []field{{"four"}, {"five"}, {"six"}}  
    for _, v := range data2 {  
       go (*field).print(&v)  
    }  
    time.Sleep(3 * time.Second)  
}

运行单测,看下结果:

0xc000026590 six 
0xc000026590 six 
0xc000026590 six 
0xc000026540 two 
0xc000026530 one 
0xc000026550 three 

如果你奇怪为什么第二段代码输出的全是 six,那么可以接着看下面的原因分析。

先要知道的是, for range 循环中会重复使用同一个变量 v

在第一段,data1 是一个指针数组([]*field),v 每次迭代时是指向数组中不同元素的指针。由于 v 是指针,每个元素的地址自然是不同的,因此打印出的地址不同,每个元素都可以打印出来。

在第二段,data2 是一个结构体数组([]field),而 v 是数组元素的副本。当你取 &v 时,每次获取的都是这个循环中的同一个变量的地址,所以 &v 的地址是相同的。但每次迭代时,这个地址指向的值会被更新为 data2 中当前元素的副本,在最后执行的时候,&v 执行的值就是 six

如果想让第二段输出所有元素,可以每次迭代拷贝一个 v 副本,复制给变量 s,如下:

for _, v := range data2 {  
    s := v  
    go (*field).print(&s)  
}

执行单测,输出结果如下:

0xc0000265d0 six 
0xc000026590 four 
0xc0000265b0 five 
0xc000026540 two 
0xc000026550 three 
0xc000026530 one 

在这个例子中,每次迭代都会创建新的变量 s,内存地址是不同的,所以可以全部输出。

Over!


http://www.kler.cn/news/365153.html

相关文章:

  • 设计师的新宠:7款不容错过的界面设计软件
  • K最近邻算法
  • 2024年双十一有什么好物推荐?盘点2024双十一爆款好物分享
  • 盘古信息制造数字化优才计划 | 解决人才困境 赋能智能制造
  • 国家能源集团携手海康威视研发攻克融合光谱煤质快检技术
  • 密码学原理
  • 考研读研生存指南,注意事项
  • useEffect简单介绍
  • java -jar启动 报错: Error: Unable to access jarfile
  • NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备的多维拓展与灵活应用
  • 基于SSM+微信小程序的家政服务管理系统(家政2)
  • 批量归一化(Batch Normalization)
  • 混个1024勋章
  • [笔记] 关于CreateProcessWithLogonW函数创建进程
  • Linux系统
  • 鸿蒙到底是不是纯血?到底能不能走向世界?
  • 蓝桥杯题目理解
  • Python爬虫:urllib_ajax的get请求豆瓣电影前十页(08)
  • 【C++】用哈希桶模拟实现unordered_set和unordered_map
  • 网络安全中的日志审计:为何至关重要?
  • 35.第二阶段x86游戏实战2-C++遍历技能
  • CPRI与eCPRI的区别
  • 每天5分钟玩转C#/.NET之C#语言详细介绍
  • python-PyQt项目实战案例:制作一个视频播放器
  • 双十一送你一份购物攻略,绿联NAS DXP2800评测
  • 借老系统重构我给jpa写了个mybatis风格的查询模块