Go语言 值传递

news2024/11/23 9:19:05

官方说法,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 := 13
	var i *int = &a

	ch := 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.Pointer
	len   int
	cap   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.coloradmin.cn/o/1271358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免费SSL证书有效果吗?

首先,我们要明确一点:无论是付费还是免费的SSL证书,它们都能实现基本的HTTPS加密功能,确保数据在客户端和服务器之间的传输过程中不会被窃取或篡改。从这个角度来看,免费SSL证书的确可以提供一定的安全保障。 然而&…

每天一点python——day81

#每天一点Python——81 #递归函数: 递归函数: 一个函数在该函数体内调用了该函数本身,这个函数称为递归函数 【释:我自己调用自己的函数】 递归函数的组成部分: 递归调用与递归终止条件。 【一定有一个跳出循环的终止条…

【MySQL】事务(事务四大特性+四种隔离级别+MVCC)

事务 前言正式开始事务的四大特性为什么会出现事务事务的版本支持事务提交方式事务常见操作方式启动事务回滚演示提交事务事务的异常autocommit 事务的隔离性隔离级别查看隔离级别修改隔离级别验证四种隔离级别读未提交(read uncommitted) —— 缩写为RU读提交(read committed)…

web:catcat-new(文件包含漏洞、flask_session伪造)

前提知识 /etc/passwd 该文件储存了该Linux系统中所有用户的一些基本信息,只有root权限才可以修改。其具体格式为 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell(以冒号作为分隔符) /proc/self proc是一个伪文件系统…

C++和Python混合编程在数据采集程序中的应用

目录 一、引言 二、C和Python的特性及其在数据采集程序中的应用 1、C的特性及其在数据采集程序中的应用 2、Python的特性及其在数据采集程序中的应用 三、C和Python混合编程在数据采集程序中的实现方法 四、混合编程的优缺点以及未来发展趋势 五、代码示例 六、结论 一…

创业者如何在居家办公和固定办公场地办公中权衡利弊,选择合适的

创业者如何选择办公方式,可能要根据自己的创业项目、团队规模、资金情况、行业特点等多方面因素来考虑。一般来说,居家办公和固定办公场地各有优缺点,没有绝对的好坏,只有适合不适合。我为大家总结了一些相关的信息,希…

Spring-AOP与声明式事务

为什么要用AOP ①现有代码缺陷 针对带日志功能的实现类,我们发现有如下缺陷: 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力 附加功能分散在各个业务功能方法中,不利于统一维护 ②解决思路 解决这两个问题&…

原来字节跳动这么容易就能进····

“字节是大企业,是不是很难进去啊?” “在字节做软件测试,能得到很好的发展吗? 一进去就有12.5K,其实也没有想的那么难” 直到现在,心情都还是无比激动! 本人211非科班,之前在字节…

SpringBoot查询指定范围内的坐标点

使用Redis geo实现 redis geo是基于Sorted Set来实现的 Redis 3.2 版本新增了geo相关命令,用于存储和操作地理位置信息。提供的命令包括添加、计算位置之间距离、根据中心点坐标和距离范围来查询地理位置集合等,说明如下: geoadd:添加地理…

海光信息荣获ESG金牛科技引领奖!

11月24日,由中国证券报、中国国新控股有限责任公司、南通市人民政府联合主办的”2023金牛企业可持续发展论坛暨第一届国新杯ESG金牛奖颁奖典礼”在江苏南通举行,“国新杯ESG金牛奖”多项获奖名单同期揭晓。海光信息凭借卓越的技术创新实力与行业影响力&a…

Facebook的这份开源协议使React四面楚歌

如果你觉得一些科技公司看起来很美好,每天都在“改变世界”……你应该看看他们的用户条款和法律文书,藏污纳垢之严重令人震惊。 最近,百度和阿里巴巴内部的软件工程团队不约而同做了一件事——弃用 React。 解释下: React 是一个…

可爱IP挖掘方法论,打造内容界的“显眼包”

近些年,人们十分乐意为“可爱”买单。玲娜贝儿、loopy、卡皮巴拉……社交平台的萌系表情包大行其道,皆源于可爱事物抚慰心灵的奇效。小红书话题「大人也要玩玩具」近90天互动量突破13亿,一跃成为年轻人的热门趋势。 本期千瓜将从行业案例出发…

AI Agents 闭门研讨会报名丨CAMEL、AutoAgents、Humanoid agents作者参与

青源Workshop丨No.27 AI Agents主题闭门研讨会 所谓AI智能体(AI Agents),是一种能够感知环境、进行决策和执行动作的智能实体。它们拥有自主性和自适应性,可以依靠AI赋予的能力完成特定任务,并在此过程中不断对自我进行…

Apache Doris 整合 FLINK CDC 、Paimon 构建实时湖仓一体的联邦查询入门

1.概览 多源数据目录(Multi-Catalog)功能,旨在能够更方便对接外部数据目录,以增强Doris的数据湖分析和联邦数据查询能力。 在之前的 Doris 版本中,用户数据只有两个层级:Database 和 Table。当我们需要连…

应用软件快速开发平台,一起实现办公流程化发展!

做好办公流程化发展能给企业带来什么好处?其实,在快节奏发展社会中,很多企业的规模和业务量也在不断扩展中,如果还是懒散的办公方式是不能达到事半功倍的效果的。要想实现高效率发展,采用办公流程化发展能让企业管理朝…

一文让你深入了解JavaSE的知识点

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

leetcode:循环队列

题目描述 题目链接:622. 设计循环队列 - 力扣(LeetCode) 题目分析 我们开辟空间的时候多开一个,k是队列的长度,我们开k1个空间,定义一个front指向头,back的下一个指向尾 当frontback的时候&am…

富必达API:一站式无代码开发集成电商平台、CRM和营销系统

一站式无代码开发的连接解决方案 电子商务、客户服务系统以及其它商业应用,是现代企业运营的重要部分。然而,将这些系统进行有效的整合往往需要复杂的API开发,这对很多企业来说是一个巨大的挑战。富必达API以其一站式的无代码开发解决方案&a…

如何做好自动化测试?

提起自动化测试,可谓仁者见人,智者见智,心中五味杂陈啊!你从任何一个招聘渠道来看最近两年对测试岗位的要求,几乎都要求会自动化测试。 而不少人一直认为手工测试才是王道,工作中有的时候也用不到程序&…

Pycharm使用远程服务器运行本地python文件

一、连接远程服务器 路径:Tools → Deployment → Configuration → SFTP → 取名 填写配置信息 二、配置python解释器 三、运行python文件