关于Go 是传值还是传引用?很多人都讨论起来
下面我们就带着问题一起探索答案吧
1、Go 官方的定义
本部分引用 Go 官方 FAQ 的 “When are function parameters passed by value?”,内容如下。
如同 C 系列的所有语言一样,Go 语言中的所有东西都是以值传递的。也就是说,一个函数总是得到一个被传递的东西的副本,就像有一个赋值语句将值赋给参数一样。
例如:
int int map slice map slice map slice
划重点:Go 语言中一切都是值传递,没有引用传递。不要直接把其他概念硬套上来,会犯先入为主的错误的。
2、传值和传引用
2.1 传值
pass by value
简单来讲,值传递,所传递的是该参数的副本,是复制了一份的,本质上不能认为是一个东西,指向的不是一个内存地址。
案例一如下:
输出结果:
main 内存地址:0xc000116220
hello 内存地址:0xc000132020
main 0xc000116220hello 0xc000132020
main
案例二如下:
hello main
输出结果:
main 内存地址:0xc000010240
hello 内存地址:0xc00000e030
煎鱼进脑子了
输出的结果是 “煎鱼进脑子了”。这时候大家可能又犯嘀咕了,煎鱼前面明明说的是 Go 语言只有值传递,也验证了两者的内存地址,都是不一样的,怎么他这下他的值就改变了,这是为什么?
因为 “如果传过去的值是指向内存空间的地址,那么是可以对这块内存空间做修改的”。
s
2.2 传引用
pass by reference
在 Go 语言中,官方已经明确了没有传引用,也就是没有引用传递这一情况。
因此借用文字简单描述,像是例子中,即使你将参数传入,最终所输出的内存地址都是一样的。
3、争议最大的 map 和 slice
map slice
map slice map slice
3.1 map
针对 map 类型,进一步展开来看看例子:
输出结果:
main 内存地址:0xc00000e028
hello 内存地址:0xc00000e038
确实是值传递,那修改后的 map 的结果应该是什么。既然是值传递,那肯定就是 "这次一定!",对吗?
输出结果:
map[脑子进煎鱼了:记得点赞!]
结果是修改成功,输出了 “记得点赞!”。这下就尴尬了,为什么是值传递,又还能做到类似引用的效果,能修改到源值呢?
这里的小窍门是:
map runtime hmap map
hello hello
这类情况我们称其为 “引用类型”,但 “引用类型” 不等同于就是传引用,又或是引用传递了,还是有比较明确的区别的。
在 Go 语言中与 map 类型类似的还有 chan 类型:
一样的效果。
3.2 slice
针对 slice 类型,进一步展开来看看例子:
输出结果:
main 内存地址:0xc000098180
hello 内存地址:0xc000098180
[煎鱼 咸鱼 摸鱼]
从结果来看,两者的内存地址一样,也成功的变更到了变量 s 的值。这难道不是引用传递吗,煎鱼翻车了?
关注两个细节:
& %p
之所以可以同时做到上面这两件事,是因为标准库 fmt 针对在这一块做了优化:
value.Pointer
标准库 fmt 能够输出 slice 类型对应的值的原因也在此:
Data slice SliceHeader%p slice
slice
其实和输出的原理是一样的,在 Go 语言运行时,传递的也是相应 slice 类型的底层数组的指针,但需要注意,其使用的是指针的副本。严格意义是引用类型,依旧是值传递。
妙不妙?
3、总结
在今天这篇文章中,我们针对 Go 语言的日经问题:“Go 语言到底是传值(值传递),还是传引用(引用传递)” 进行了基本的讲解和分析。
slicemapchan
这实则是不大对的认知,因为:“如果传过去的值是指向内存空间的地址,是可以对这块内存空间做修改的”。
其确实复制了一个副本,但他也借由各手段(其实就是传指针),达到了能修改源数据的效果,是引用类型。
石锤,Go 语言只有值传递,