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.mzph.cn/news/187186.shtml

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

相关文章

Logstash使用指南

介绍 Logstash是一个开源数据收集引擎,具有实时管道功能。它可以动态地将来自不同数据源的数据统一起来,并将数据标准化到你所选择的目的地。尽管Logstash的早期目标是搜集日志,现在它的功能已完全不只于此。任何事件类型都可以加入分析&…

docker (镜像分层、阿里云镜像推送/拉去)-day02

一、镜像概念 Docker 镜像是 Docker 容器的基础,它提供了一种可重复使用的、跨平台的部署方式,使得应用程序的部署和运行变得简单和高效。 把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),打包好…

C语言练习记录(蓝桥杯练习)(小蓝数点)

目录 小蓝数点 第一题程序的输出结果是?: 第二题下面代码的执行结果是什么?: 第三题下面代码的执行结果是什么?: 第四题关于关系操作符说法错误的是?: 第五题对于下面代码段,y的值为? 第六题sum 21 …

Java 数据结构篇-用链表、数组实现栈

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 栈的说明 2.0 用链表来实现栈 2.1 实现栈 - 入栈方法(push) 2.2 实现栈 - 出栈(pop) 2.3 实现栈 - 查看栈顶元素…

国产linux单用户模式破解无密码登陆 (麒麟系统用户登录密码遗忘解决办法)

笔者手里有一批国产linu系统,目前开始用在日常的工作生产环境中,我这个老程序猿勉为其难的充当运维的或网管的角色。 国产linux系统常见的为麒麟Linux,统信UOS等,基本都是基于debian再开发的linux。 问题描述: 因为…

基于AT89C51单片机的倒数计时器设计

1.设计任务 利用AT89C51单片机为核心控制元件,设计一个简易的数字电压表,设计的系统实用性强、操作简单,实现了智能化、数字化。 本设计采用单片机为主控芯片,结合周边电路组成LED彩灯的闪烁控制系统器,用来控制红色…

用于缓存一些固定名称的小组件

项目中,用于缓存姓名、地名、单位名称等一些较固定名称的id-name小组件。用于减少一些表的关连操作和冗余字段。优化代码结构。扩展也方便,写不同的枚举就行了。 具体用法: {NameCacheUser.USER.getName(userId);NameCacheUser.ACCOUNT.getN…

excel合并单元格教程

在表格里,总是会遇到一级表格、二级表格的区别,这时候一级表格会需要合并成一个大格子,那么excel如何合并单元格呢,其实使用快捷键或者功能键就可以了。 excel如何合并单元格: 1、首先我们用鼠标选中所有要合并的单元…

最大公约数的C语言实现xdoj31

时间限制: 1 S 内存限制: 1000 Kb 问题描述: 最大公约数(GCD)指某几个整数共有因子中最大的一个,最大公约数具有如下性质, gcd(a,0)a gcd(a,1)1 因此当两个数中有一个为0时,gcd是不为0的那个整数&#xff…

XXL-Job详解(一):组件架构

目录 XXL-Job特性系统组成架构图调度模块剖析任务 “运行模式” 剖析执行器 XXL-Job XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。 特性 1、简单&#…

java+springboot实验室管理系统的设计与实现ssm+jsp

课题研究内容: (1) 系统需求分析(构成模块,系统流程,功能结构图,系统需求) (2) 实验室课程安排功能模块(课程的录入和调补) &#xff…

wyler水平仪维修WYLER倾角仪维修CH-8405

瑞士WYLER电子水平仪维修;BIueCLINO倾斜度测量仪维修;wyler电子倾角仪维修。 水平仪常见故障及处理方法 1、 仪表通电不工作。 A、检查仪表220V电源端子接线是否正确 B、检查仪表电容是否熔断; C、拧下仪表后的固定螺钉,将表…

王道数据结构课后代码题p40 4.在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值唯一) (c语言代码实现)

本题代码为 void deletemin(linklist* L)//找到最小值并删除 {lnode* p (*L)->next, * pre *L;lnode* s p,*sprepre;while (p ! NULL)//找到最小值{if (p->data < s->data){s p;spre pre;}p p->next;pre pre->next;}p s->next;spre->next p;…

有IP没有域名可以申请证书吗?

一、IP证书是什么&#xff1f; ip证书是用于公网ip地址的SSL证书&#xff0c;与我们通常所讲的SSL证书并无本质上的区别&#xff0c;但由于SSL证书通常颁发给域名&#xff0c;而组织机构需要公共ip地址的SSL证书&#xff0c;这类SSL证书就是我们所说的ip证书。ip证书具有安全、…

仅仅通过提示词,GPT-4可以被引导成为多个领域的特定专家

The Power of Prompting&#xff1a;提示的力量&#xff0c;仅通过提示&#xff0c;GPT-4可以被引导成为多个领域的特定专家。微软研究院发布了一项研究&#xff0c;展示了在仅使用提策略的情况下让GPT 4在医学基准测试中表现得像一个专家。研究显示&#xff0c;GPT-4在相同的基…

上海毅速丨新材料将推动3D打印在压铸行业的应用

压铸是一种应用广泛的制造工艺&#xff0c;它的制造原理是将液态或半液态金属&#xff0c;在高压作用下&#xff0c;以高速度填充压铸模具型腔&#xff0c;并在压力下快速凝固而获得铸件的一种方法。压铸模的设计和制造需要考虑到多方面的因素&#xff0c;如模具材料、结构、冷…

设计模式详解(二):抽象工厂——Abstract Factory

目录导航 抽象工厂及其作用工厂方法的好处工厂方法的实现关系图实现步骤 工厂方法的适用场景工厂方法举例 抽象工厂及其作用 工厂方法是一种创建型设计模式。所谓创建型设计模式是说针对创建对象方面的设计模式。在面向对象的编程语言里&#xff0c;我们通过对象间的相互协作&…

SAP SD 创建交货单 报错 VL461 VL248

因为生产环境已经被改好了&#xff0c;无法跟踪 所以换到测试环境重现一把&#xff0c;如何追根究底 对比正常订单发现 计划行 VBEP-LMENG,VBEP-BMENG这两个字段上的值跟 订单数量不一致。 尝试修改2者的数据跟订单数据一致&#xff0c;则可以正常创建交货单 实际原因是&a…

【每日一坑】KiCAD导Gerber 文件

第一&#xff0c;软件版本 第二、操作选择注意点 第三&#xff0c;那个坑。。。 不知道什么原因&#xff0c;这第二个框里会选其他不必要得一些东西&#xff0c;导致最终光绘是废的&#xff1b; 一定切记只选edge.cut就好了。 最后&#xff0c;上Gerber图&#xff0c;还有一点…

机器学习入门(第六天)——支持向量机(升维打击)

Support vector machines 知识树 Knowledge tree 苹果表示重点 间隔&#xff1a;使用了几何间隔&#xff0c;保证w b的度量&#xff0c;感知机则是函数间隔 间隔最大化思想&#xff1a;则是支持向量机的独有&#xff0c;这使得它找到最优超平面 核函数&#xff1a;面试当中可…