Go语言 值传递

官方说法,Go中只有值传递,没有引用传递

而Go语言中的一些让你觉得它是引用传递的原因,是因为Go语言有值类型引用类型,但是它们都是值传递

值类型 有int、float、bool、string、array、sturct等

引用类型有slice,map,channel,interface,func等

值类型:内存中变量存储的是具体的值。 比如: var num int 。num存放的是具体的int值,但是变量在内存中的地址可以通过 &num 来获取。

引用类型:变量直接存放的就是一个地址值,这个地址值指向的空间存的才是值。

代码测试:

func main() {slice := []int{1, 2, 3}arr := [2]int{1, 2}m := make(map[string]string)a := 13var i *int = &ach := make(chan string)fmt.Printf("[main array] %p\n", &arr)fmt.Printf("[main pointer] %p\n", &i)fmt.Printf("[main map] %p\n", &m)fmt.Printf("[main slice] %p\n", &slice)fmt.Printf("[main chan] %p\n", &ch)fmt.Printf("[main slice 第一个元素的地址: ] %p\n", &slice[0])fmt.Println()get(arr, slice, m, i, ch)
}func get(arr [2]int, s []int, m map[string]string, i *int, ch chan string) {fmt.Printf("[main array] %p\n", &arr)fmt.Printf("[get pointer] %p\n", &i)fmt.Printf("[get map] %p\n", &m)fmt.Printf("[get slice] %p\n", &s)fmt.Printf("[get chan] %p\n", &ch)fmt.Printf("[get slice 第一个元素的地址: ] %p\n", &s[0])
}

测试结果:

可以发现,数组、slice、map、chan、指针在传递过程中,地址都发生了变化。这说明传递的是一份拷贝。这里需要特意强调切片的第一个元素的地址前后没有发生改变

但是我们在日常写go代码时发现,在函数里修改slice、map,函数外的值也会改变,这是为什么呢?

那接下来就逐个分析下。源码版本是1.21.3,这里就只是查看下源码创建slice,map时的返回值而已,不会讲解过多的源码内容。

引用类型分析

slice

 slice 是一个长度可变的连续数据序列,其是个结构体,其中包含的字段包括:指向内存空间地址起点的指针 array、一个表示了存储数据长度的 len 和分配空间长度的 cap。

type slice struct {array unsafe.Pointerlen   intcap   int
}func makeslice(et *_type, len, cap int) unsafe.Pointer {    .....................return mallocgc(mem, et, true)
}

创建slice时候,返回的是数组地址

那么 slice 在传递过程中,本质上传递的是 slice实例中的内存地址 array。

因为slice是引用类型,指向的是同一个数组。也通过前面的测试代码结果,可以看到,在函数内外,slice本身的地址&slice变了,但是两个指针指向的底层数据,也就是&slice[0]数组首元素的地址是不变的

所以在函数内部的修改可以影响到函数外部,这个很容易理解。

那再来看看对slice使用append。代码如下

func main() {arr := make([]int, 0) //容量cap不够的情况// arr := make([]int, 0, 5) //容量cap足够的情况arr = append(slice, 2, 4)fmt.Printf("main1 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%d\n", &arr, &arr[0], len(arr), cap(arr))appendSlice(arr)fmt.Printf("main2 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%d\n", &arr, &arr[0], len(arr), cap(arr))
}func appendSlice(arr []int) {fmt.Printf("传递参数后,append前 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%d\n", &arr, &arr[0], len(arr), cap(arr))arr = append(arr, 1)fmt.Println()fmt.Printf("append后 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%d\n", &arr, &arr[0], len(arr), cap(arr))
}

主要就有两种情况:

切片make不够容量

即是append时需要扩容

1.首先,外部传入一个slice,引用类型。

2.也还是值传递(slice地址发生了改变),但是两个arr指向的底层数组首元素&arr[0]没有改变,也就是array unsafe.Pointer不变。

3.在内部调用append,因为cap容量不够,要扩容,重新在新的地址空间分配底层数组,所以数组首元素的地址改变了

4.回到函数外部,外部的slice指向的底层数组为原数组,内部的修改不影响原数组

切片make够容量

结果一样是[2 4],虽然函数内部append的结果同样不影响外部的输出,但是原理却不一样。

不同之处:

  • 在内部调用append的时候,由于cap容量足够,所以不需要扩容,在原地址空间增加一个元素即可,所以底层数组的首元素地址相同
  • 回到函数外部,打印出来还是[2 4],是因为外层的len是2,所以只能打印2个元素,实际上第3个元素的地址上已经有数据了。只不过因为len为2,所以我们无法看到第3个元素。

 

 不管cap容量够不够,其都没有改变外部slice的len和cap,所以最终看到的slice的len和cap都是没有改变的。

想要改变长度的的话,要传slice的指针

传指针进去,拷贝的就是这个指针。指针指向的对象,也就是slice本身,是不变的。

func appendSlicePointer(arr *[]int) {*arr = append(*arr, 5)
}

map

map使用的时候都是通过make来创建,例如

mymap := make(map[int]string)

 通过查看源码我们可以看到,实际上make底层调用的是makemap函数。而在src/runtime/hashmap.go源代码305行发现,makemap函数返回的是一个hmap类型的指针*hmap。也就是说map===*hmap。hmap是个结构体。

// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap {.......................return h
}

而对于指针类型的参数来说,只是复制了指针本身,指针所指向的地址还是之前的地址。所以对map的修改是可以影响到函数外部的。

chan

管道的创建

channel := make(chan int, 5)

通过查看src/runtime/chan.go源代码72行发现,makechan函数返回的是一个hchan类型的指针*hchan。hchan也是个结构体。所以chan类型和map类型是本质是一样的。

func makechan(t *chantype, size int) *hchan {.................var c *hchan.....................return c
}

总结:

1.Go中只有值传递,没有引用传递

2.如果需要函数内部的修改能影响到函数外部,那就传指针

3.map/chan本身是指针,是引用类型,直接传其本身即可

4.slice 在传递过程中,本质上传递的是其内存地址 array,也即是指针,直接传slice本身即可

5.slice的append操作需要修改结构体的len或者cap,类似于struct。若要影响到函数外部,需要传指针,或通过函数返回值返回结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/235961.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FlatLaf:干净、优雅、扁平化,基于java swing现代开源跨平台外观

一个很不错的java swing ui库,idea主题风格,还能自定义 FlatLaf是用于JavaSwing 桌面应用程序的现代开源跨平台外观。 它看起来几乎是平的(没有阴影或渐变)、干净、简单和优雅。FlatLaf带有Light、Dark、IntelliJ和Darcula主题&a…

11 款顶级的免费 iPhone 数据恢复软件

iPhone 拥有巨大的存储容量。您可以在 iPhone 设备上存储图像、文档和视频等数据。有时,您的 iPhone 会发生许多意外事件,例如意外删除,从而导致数据丢失。这里有 11 个最好的免费 iPhone 数据恢复软件,您可以免费下载&#xff0c…

[架构之路-255]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 软件架构风格

前言: 风格是指在不同领域内,人们在表达自己的过程中(如艺术、音乐、文化、时尚、建筑、软件系统等),所选择的、相对稳定的表达方式和特征的总和。在不同领域内都存在着多种不同的风格。 在艺术领域内,也…

zabbix分布式监控平台从IPV4切换到IPV6之监控主机切换

现在有一套监控了海量服务器的zabbix分布式监控平台需整体在线从IPV4切换到IPV6,不能影响其原有的定制监控及视图。本文讲解了切换的第一步--监控主机切换。 一、zabbix分布式监控平台平台架构 本套zabbix分布式监控平台是一个多代理服务器分布式部署的典型传统架构…

和鲸科技与国科环宇建立战略合作伙伴关系,以软硬件一体化解决方案促进科技创新

近日,在国科环宇土星云算力服务器产品发布会暨合作伙伴年度会上,和鲸科技与国科环宇正式完成战略伙伴签约仪式,宣布达成战略合作伙伴关系。未来,双方将深化合作,充分发挥在产品和市场方面的互补优势,为企事…

Pycharm配置jupyter使用notebook详细指南(可换行conda环节)

本教程为事后记录,部分图片非实操图片。 详细记录了pycharm配置jupyter的方法,jupyter添加其他conda环境的方法,远程密码调用jupyter的方法,修改jupyter工作目录的方法。 文章目录 一、入门级配置1. Pycharm配置Conda自带的jupyt…

Goby 漏洞发布| CrushFTP as2-to 认证权限绕过漏洞(CVE-2023-43177)

漏洞名称: CrushFTP as2-to 认证权限绕过漏洞(CVE-2023-43177) English Name:CrushFTP as2-to Authentication Permission bypass Vulnerability (CVE-2023-43177) CVSS core: 9.8 影响资产数: 38695 漏洞描述&…

C语言贪吃蛇(有详细注释)

这个贪吃蛇是在比特特训营里学到的,同时我还写了用EasyX图形库实现的图形化贪吃蛇,含有每个函数的实现以及游戏中各种细节的讲解,感兴趣的可以去看一看。 贪吃蛇小游戏 实现效果 以下就是源码,感兴趣的小伙伴可以cv自己玩一玩改…

【【FPGA的 MicroBlaze 的 介绍与使用 】】

FPGA的 MicroBlaze 的 介绍与使用 可编程片上系统(SOPC)的设计 在进行系统设计时,倘若系统非常复杂,采用传统 FPGA 单独用 Verilog/VHDL 语言进行开发的方式,工作量无疑是巨大的,这时调用 MicroBlaze 软核…

使用gparted进行ubuntu虚拟机的磁盘扩容(解决gparted无法拖动分区的问题)

在学习内核编译下载linux内核源码的时候,由于源码非常大,下载的时候提示磁盘空间不足,我才意识到刚开始创建虚拟机的时候分配了20GB的空间现在已经快用光了。在VM的设置里可以进行扩容,我扩展到了30GB重启却发现空间并没有加到我使…

信贷销售经理简历模板

这份简历内容,以信贷销售经理招聘需求为背景,我们制作了1份全面、专业且具有参考价值的简历案例,大家可以灵活借鉴。 信贷销售经理简历模板在线编辑下载:百度幻主简历 求职意向 求职类型:全职 意向岗位&#xff…

C语言之结构体详解

C语言之结构体详解 文章目录 C语言之结构体详解1. 结构体类型的声明2. 结构体变量的创建和初始化3. 结构体的特殊声明4. 结构体的自引用结构体的自引用匿名结构体的自引用 5. 结构体内存对齐5.1 练习一5.2 练习三 6. 为什么存在内存对⻬? 1. 结构体类型的声明 struct tag {me…