[编程技术] Golang是值传递还是引用传递
作者:CC下载站 日期:2022-07-02 13:00:51 浏览:21 分类:编程开发
背景
先说结论,Go里面没有引用传递
,Go语言是值传递
。很多技术博客说Go语言有引用传递,都是没真的理解Go语言。
值传递
指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递
指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
而Go语言中的一些让你觉得它是引用传递
的原因,是因为Go语言有值类型
和引用类型
,但是它们都是值传递
。
值类型
- int、float、bool、string、array、sturct等
引用类型
-
slice,map,channel,interface,func等
-
引用类型作为参数时,称为浅拷贝,形参改变,实参数跟随变化.因为传递的是地址,形参和实参都指向同一块地址
-
值类型作为参数时,称为深拷贝,形参改变,实参不变,因为传递的是值的副本,形参会新开辟一块空间,与实参指向不同
-
如果希望值类型数据在修改形参时实参跟随变化,可以把参数设置为指针类型
如果对Go语言只有值传递
有不同想法的,请看官网的解释。
When are function parameters passed by value?
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. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to. (See a later section for a discussion of how this affects method receivers.)
Map and slice values behave like pointers: they are descriptors that contain pointers to the underlying map or slice data. Copying a map or slice value doesn’t copy the data it points to. Copying an interface value makes a copy of the thing stored in the interface value. If the interface value holds a struct, copying the interface value makes a copy of the struct. If the interface value holds a pointer, copying the interface value makes a copy of the pointer, but again not the data it points to.
我来翻译一下:
像 C 家族中的其他所有语言一样,Go 语言中的所有传递都是传值。
也就是说,函数接收到的永远都是参数的一个副本,就好像有一条将值赋值给参数的赋值语句一样。
例如,传递一个 int 值给一个函数,函数收到的是这个 int 值得副本,传递指针值,获得的是指针值的副本,而不是指针指向的数据。
(请参考 [later section] (https://golang.org/doc/faq#methods_on_values_or_pointers) 来了解这种方式对方法接收者的影响)
Map 和 Slice 的值表现和指针一样:它们是对内部映射或者切片数据的指针的描述符。
复制映射或者切片的值,不会复制它们指向的数据。复制接口的值,会产生一个接口值存储的数据的副本。
如果接口值存储的是一个结构体,复制接口值将产生一个结构体的副本。
如果接口值存储的是指针,复制接口值会产生一个指针的副本,而不是指针指向的数据的副本。
值传递
这里列出典型的值传递的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
i := 1
str := "old"
stu := student{name: "ada", age: 1}
modify(i, str, stu)
fmt.Println(i, str, stu.age) //1 old 1
}
func modify(i int, str string, stu student) {
i = 5
str = "new"
stu.age = 10
}
可以发现,在函数里面修改了值之后,不会影响函数外的变量的值。
我们想要内部修改能影响到函数外的变量的值,怎么办呢?
答案是:传指针
。
因为传指针的值传递,复制的是指针本身,但是指针指向的地址是一样的。所以我们在函数内部的修改,能影响到函数外的变量的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
i := 1
str := "old"
stu := &student{name: "ada", age: 1}
modify(&i, &str, stu)
fmt.Println(i, str, stu.age) //5 new 10
}
func modify(i *int, str *string, stu *student) {
*i = 5
*str = "new"
stu.age = 10
}
注意这可不是引用传递
,只是因为我们传入的是指针,指针本身是一份拷贝,但是对这个指针解引用
之后,也就是指针所指向的具体地址,是不变的,所以函数内部的修改,在函数外面是知道的。
map
了解清楚了传值和传引用,但是对于Map类型来说,可能觉得还是迷惑,一来我们可以通过函数修改它的内容,二来它没有明显的指针。
1
2
3
4
5
6
7
8
9
10
11
12
func main() {
users := make(map[int]string)
users[1] = "user1"
fmt.Printf("before modify: user:%v\n", users[1]) // before modify: user:user1
modify(users)
fmt.Printf("after modify: user:%v\n", users[1]) // after modify: user:user2
}
func modify(u map[int]string) {
u[1] = "user2"
}
我们都知道,值传递
是一份拷贝,里面的修改并不影响外面实参的值,那为什么map在函数内部的修改可以影响外部呢?
通过查看源码我们可以看到,实际上make
底层调用的是makemap
函数,主要做的工作就是初始化hmap
结构体的各种字段
1
2
3
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
//...
}
通过查看src/runtime/hashmap.go
源代码发现,make
函数返回的是一个hmap
类型的指针*hmap
。也就是说map===*hmap
。 现在看func modify(p map)
这样的函数,其实就等于func modify(p *hmap)
,相当于传递了一个指针进来。
而对于指针类型的参数来说,只是复制了指针本身,指针所指向的地址还是之前的地址。所以对map的修改是可以影响到函数外部的。
chan类型
chan
类型本质上和map
类型是一样的,这里不做过多的介绍,参考下源代码:
1
2
3
func makechan(t *chantype, size int64) *hchan {
//...
}
chan
也是一个引用类型,和map
相差无几,make
返回的是一个*hchan
。
slice类型
而map和chan使用make函数返回的实际上是 *hmap
和*hchan
指针类型,也就是指针传递。
slice虽然也是引用类型,但是它又有点不一样。
简单来说就是,slice本身是个结构体,但它内部第一个元素是一个指针类型,指向底层的具体数组,slice在传递时,形参是拷贝的实参这个slice,但他们底层指向的数组是一样的,拷贝slice时,其内部指针的值也被拷贝了,也就是说指针的内容一样,都是指向同一个数组。
我们先看一个简单的例子,对slice
的某一元素进行赋值。
1
2
3
4
5
type slice struct {
array unsafe.Pointer
len int
cap int
}
下面举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
arr := make([]int, 0)
arr = append(arr, 1, 2, 3)
fmt.Printf("outer1: %p, %p\n", &arr, &arr[0])
modify(arr)
fmt.Println(arr) // 10, 2, 3
}
func modify(arr []int) {
fmt.Printf("inner1: %p, %p\n", &arr, &arr[0])
arr[0] = 10
fmt.Printf("inner2: %p, %p\n", &arr, &arr[0])
}
//输出:
//outer1: 0x14000112018, 0x14000134000
//inner1: 0x14000112030, 0x14000134000
//inner2: 0x14000112030, 0x14000134000
//[10 2 3]
因为slice
是引用类型,指向的是同一个数组。
可以看到,在函数内外,arr本身的地址&arr
变了,但是两个指针指向的底层数据,也就是&arr[0]
数组首元素的地址是不变的。
所以在函数内部的修改可以影响到函数外部,这个很容易理解。
再来看另外一个稍微复杂的例子,函数内部使用append
。这个会稍微不一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
arr := make([]int, 0)
//arr := make([]int, 0, 5)
arr = append(arr, 1, 2, 3)
fmt.Printf("outer1: %p, %p, len:%d, capacity:%d\n", &arr, &arr[0], len(arr), cap(arr))
//modify(arr)
appendSlice(arr)
fmt.Printf("outer2: %p, %p, len:%d, capacity:%d\n", &arr, &arr[0], len(arr), cap(arr))
fmt.Println(arr)
}
func appendSlice(arr []int) {
fmt.Printf("inner1: %p, %p, len:%d, capacity:%d\n", &arr, &arr[0], len(arr), cap(arr))
//modify(arr)
arr = append(arr, 1)
fmt.Printf("inner2: %p, %p, len:%d, capacity:%d\n", &arr, &arr[0], len(arr), cap(arr))
//modify(arr) //&arr[0]的地址是否相等,取决于初始化slice的时候的capacity是否足够
}
这个问题就相对复杂的多了。
分两种情况:
make slice的时候没有分配足够的capacity
arr := make([]int, 0)
像这种写法,那么输出就是:
outer1: 0x14000114018, 0x1400012e000, len:3, capacity:3
inner1: 0x14000114030, 0x1400012e000, len:3, capacity:3
inner2: 0x14000114030, 0x1400012c060, len:4, capacity:6
outer2: 0x14000114018, 0x1400012e000, len:3, capacity:3
[1 2 3]
- outer1: 外部传入一个
slice
,引用类型,值传递。 - inner1: 由于是值传递,所以arr的地址
&arr
变了,但是两个arr指向的底层数组首元素&arr[0]
,也就是array unsafe.Pointer
。 - inner2: 在内部调用
append
后,由于cap容量
不够,所以扩容,cap=cap*2
,重新在新的地址空间分配底层数组,所以数组首元素的地址改变了。 - 回到函数外部,外部的slice指向的底层数组为原数组,内部的修改不影响原数组。
make slice的时候分配足够的capacity
arr := make([]int, 0, 5)
像这种写法,那么输出就是:
outer1: 0x1400000c030, 0x1400001c050, len:3, capacity:5
inner1: 0x1400000c048, 0x1400001c050, len:3, capacity:5
inner2: 0x1400000c048, 0x1400001c050, len:4, capacity:5
outer2: 0x1400000c030, 0x1400001c050, len:3, capacity:5
[1 2 3]
虽然函数内部append
的结果同样不影响外部的输出,但是原理却不一样。
不同点:
- 在内部调用
append
的时候,由于cap 容量
足够,所以不需要扩容,在原地址空间增加一个元素,底层数组的首元素地址相同。 - 回到函数外部,打印出来还是
[1 2 3]
,是因为外层的len
是3,所以只能打印3个元素,实际上第四个元素的地址上已经有数据了。只不过因为len
为3,所以我们无法看到第四个元素。
那正确的append应该是怎么样的呢:
1
2
3
4
5
appendSlice(&arr)
func appendSlice(arr *[]int) {
*arr = append(*arr, 1)
}
传指针进去,这样拷贝的就是这个指针,指针指向的对象,也就是slice本身,是不变的,我们使用*arr
可以对slice进行操作。
总结
- Go里面没有
引用传递
,Go语言是值传递
。 - 如果需要函数内部的修改能影响到函数外部,那么就传指针。
- map/channel本身就是指针,是引用类型,所以直接传map和channel本身就可以。
- slice的赋值操作其实是针对slice结构体内部的指针进行操作,也是指针,可以直接传slice本身。
- slice的append操作同时需要修改结构体的
len/cap
,类似于struct,如果需要传递到函数外部,需要传指针。(或者使用函数返回值)
<全文完>
猜你还喜欢
- 03-29 [编程相关] Winform窗体圆角以及描边完美解决方案
- 03-29 [前端问题] has been blocked by CORS policy跨域问题解决
- 03-29 [编程相关] GitHub Actions 入门教程
- 03-29 [编程探讨] CSS Grid 网格布局教程
- 10-12 [编程相关] python实现文件夹所有文件编码从GBK转为UTF8
- 10-11 [编程算法] opencv之霍夫变换:圆
- 10-11 [编程算法] OpenCV Camshift算法+目标跟踪源码
- 10-11 [Python] python 创建 Telnet 客户端
- 10-11 [编程相关] Python 基于 Yolov8 + CPU 实现物体检测
- 03-15 [脚本工具] 使用go语言开发自动化脚本 - 一键定场、抢购、预约、捡漏
- 01-08 [编程技术] 秒杀面试官系列 - Redis zset底层是怎么实现的
- 01-05 [编程技术] 《Redis设计与实现》pdf
取消回复欢迎 你 发表评论:
- 精品推荐!
-
- 最新文章
- 热门文章
- 热评文章
[电视剧] 芈月传 【全集81集全】【未删减版】【国语中字】【2015】【HD720P】【75G】
[电视剧] 封神榜 梁丽版 (1989) 共5集 480P国语无字 最贴近原著的一版【0.98 G】
[影视] 【雪山飞孤4个版本】【1985、1991、1999、2007】【1080P、720P】【中文字幕】【167.1G】
[资料] 24秋初中改版教材全集(全版本)[PDF]
[电影] 高分国剧《康熙王朝》(2001)4K 2160P 国语中字 全46集 78.2G
[动画] 迪士尼系列动画139部 国英双语音轨 【蓝光珍藏版440GB】
[电影] 莫妮卡贝鲁奇为艺术献身电影大合集 1080P超清 双语字幕
[电影] DC电影宇宙系列合集18部 4K 高码率 内嵌中英字幕 273G
[音乐] 【坤曲/4坤时】鸡你太美全网最全,385首小黑子战歌,黄昏见证虔诚的信徒,巅峰诞生虚伪的拥护!
[音乐] 用餐背景音乐大合集 [MP3/flac]
[书籍] 彭子益医书合集 [PDF/DOC]
[游戏] 《黑神话悟空》免安装学习版【全dlc整合完整版】+Steam游戏解锁+游戏修改工具!
[动画] 《名侦探柯南》名侦探柯南百万美元的五菱星 [TC] [MP4]
[电视剧集] [BT下载][黑暗城市- 清扫魔 Dark City: The Cleaner 第一季][全06集][英语无字][MKV][720P/1080P][WEB-RAW]
[动画] 2002《火影忍者》720集全【4K典藏版】+11部剧场版+OVA+漫画 内嵌简日字幕
[剧集] 《斯巴达克斯》1-4季合集 无删减版 1080P 内嵌简英特效字幕
[CG剧情] 《黑神话:悟空》158分钟CG完整剧情合集 4K120帧最高画质
[短剧] 被下架·禁播的羞羞短剧·午夜短剧合集
[游戏] 黑神话悟空离线完整版+修改器
[图像处理] 光影魔术手v4.6.0.578绿色版
[影视] 美国内战 4K蓝光原盘下载+高清MKV版/内战/帝国浩劫:美国内战(台)/美帝崩裂(港) 2024 Civil War 63.86G
[影视] 一命 3D 蓝光高清MKV版/切腹 / 切腹:武士之死 / Hara-Kiri: Death of a Samurai / Ichimei 2011 一命 13.6G
[影视] 爱情我你他 蓝光原盘下载+高清MKV版/你、我、他她他 2005 Me and You and Everyone We Know 23.2G
[影视] 穿越美国 蓝光原盘下载+高清MKV版/窈窕老爸 / 寻找他妈…的故事 2005 Transamerica 20.8G
[电影] 《黄飞鸿》全系列合集
[Android] 开罗游戏 ▎像素风格的模拟经营的游戏厂商安卓游戏大合集
[游戏合集] 要战便战 v0.9.107 免安装绿色中文版
[书籍] 彭子益医书合集 [PDF/DOC]
[资源] 精整2023年知识星球付费文合集136篇【PDF格式】
[系统]【黑果小兵】macOS Big Sur 11.0.1 20B50 正式版 with Clover 5126 黑苹果系统镜像下载
- 最新评论
-
谢谢分享感谢ppy2016 评论于:11-05 谢谢分享感谢ppy2016 评论于:11-05 怎么没有后续闲仙麟 评论于:11-03 怎么没后续闲仙麟 评论于:11-03 有靳东!嘻嘻奥古斯都.凯撒 评论于:10-28 流星花园是F4处女作也是4人集体搭配的唯一一部!奥古斯都.凯撒 评论于:10-28 找了好久的资源,终于在这里找到了。感谢本站的资源和分享。谢谢AAAAA 评论于:10-26 找了好久的资源,终于在这里找到了。感谢本站的资源和分享。谢谢password63 评论于:10-26 找了好久的资源,终于在这里找齐了!!!!blog001 评论于:10-21 找了好久的资源,终于在这里找齐了!!!!blog001 评论于:10-21
- 热门tag