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 := 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.mfbz.cn/a/208320.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

掌握自动化测试必要的几种技能?

1.自动化测试员技能——编程语言 当我开始担任手动测试人员时,我不喜欢编码。但是,当我逐渐进入自动化领域时,对我来说很清楚,如果没有对编程语言的一些基本了解,就无法编写逻辑自动化测试脚本。 对编程有一点了解&a…

MinkowskiEngine安装

pip install torch ninjagit clone https://github.com/NVIDIA/MinkowskiEngine.git cd MinkowskiEngine安装之前先把并行安装的thread数降低,否则会导致进程卡死。 打开setup.py文件内位于142行的MAX_COMPILATION_THREADS变量值从12改成4。 export CXXg-7 python…

挖掘新兴市场:跨境电商中的战略机会

随着全球化进程的不断推进,跨境电商作为连接世界的桥梁,为企业提供了探索新兴市场的机遇。在这个充满活力和竞争的环境中,企业需要制定切实可行的战略,善于把握新兴市场的机会,实现可持续发展。 新兴市场的潜力与机遇 …

matlab基于线性二次调节器(LQR)法实现机器人路径规划可变轨迹跟踪

1、内容简介 略 可以交流、咨询、答疑 2、内容说明 基于线性二次调节器(LQR)法实现机器人路径规划可变轨迹跟踪 3、仿真分析 略 load path.mat %% 轨迹处理 % 定义参考轨迹 refPos_x path(:,1); refPos_y path(:,2); refPos [refPos_x, refPos_y];% 计算航向角和曲率 …

文字识别(OCR)专题——基于NCNN轻量级PaddleOCRv4模型C++推理

前言 PaddleOCR 提供了基于深度学习的文本检测、识别和方向检测等功能。其主要推荐的 PP-OCR 算法在国内外的企业开发者中得到广泛应用。在短短的几年时间里,PP-OCR 的累计 Star 数已经超过了32.2k,常常出现在 GitHub Trending 和 Paperswithcode 的日榜…

python简单进阶之web框架:fastapi使用教程

原文:python简单进阶之web框架:fastapi使用教程 - 知乎 这是简单进阶教程系列第四篇,本系列文章主要介绍那些可以很快上手的进阶库。 我其实学过一段时间Django框架,但是半途而废了,我觉得可能还是简单一点的框架比较适…

nc 传输目录

使用nc命令进行目录传输 接收方发送方 使用TCP连接进行文件传输,不提供数据加密或身份验证 接收方 -ip 192.168.1.200 nc -l -p 2222 | tar -xvf -发送方 发送目录 lotus tar -cf - lotus | nc 192.168.1.200 2222

Python+Requests对图片验证码的处理

Requests对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入,而且每次登录时图片验证码都是随机的;当通过request做接口登录的时候要对图片验证码进行识别出图片中的字段,然后再登录接口中使用; 通过request对图片验…

LINUX 嵌入式C编程--信号编程

基本概念 信号是事件发生时对进程的通知机制,也可以把它称为软件中断。信号与硬件中断的相似之处在于能够打断程序当前执行的正常流程,其实是在软件层次上对中断机制的一种模拟。信号提供了一种处理异步事件的方法。 信号目的 **信号的目的是用来通信…

shell编程系列(9)-使用cut选择列

文章目录 前言使用cut选择列选择特定的列 结语 前言 前面的文章介绍了sed命令,sed可以帮我们处理文本列,这边文章介绍cut命令,cut命令可以帮我们选择想要的列,在文本处理时候结合sed命令,就可以精准定位了。 cut命令是…

HuggingFace学习笔记--Model的使用

1--Model介绍 Transformer的 model 一般可以分为:编码器类型(自编码)、解码器类型(自回归)和编码器解码器类型(序列到序列); Model Head(任务头)是在base模型…

java正则表达式字母开头后面跟12位数字

字母开头后面跟12位数字 ^[A-Za-z]\d{12}$ 验证: 验证工具地址: Java正则表达式测试

学习感悟一己之言

学习感悟一己之言 学习上克服困难实际上是克服心理上或认识上的障碍的过程。所谓的理解,就是化陌生为熟悉。看不懂,一方面是因为接触的材料太陌生,即远离你当前的背景知识;另一方面是材料或讲述者的描述刻画不准确或晦涩不当。有了…

修改sublime配置让其显示文件编码格式

1、下载sublime并安装 2、点击菜单栏Preferences,然后在Preferences里面点击Setting 3、然后在跳出来的窗口添加: "show_enconding":true, 4、随便打开一个文件就可以在底部查看文件编码格式:

openbabel 安装 生成指纹方法

今日踩坑小结: openbabel 安装: 可以装,但是得在 Linux 环境下,win 环境装会报错(安装不会报错,但是生成指纹的时候会) 指纹: 在下面这个链接里,官方给出了命令行调用 o…

一篇博客带你认识泛型

目录 泛型类(Generic Class): 泛型方法(Generic Method): Java 中的泛型是一种编程机制,允许你编写可以与多种数据类型一起工作的代码,同时提供编译时类型检查以确保类型的安全性。泛型的主要目的是提高代…

外贸获客的几种正确打开方式,还不快来GET!

做外贸还在愁没客户?作为外贸人,开发客户是我们的重要工作内容,想要高效地开发客户,首先就要知道外贸获客的方法有哪些,当下最主流的外贸获客渠道分为线下和线上两种方式,今天东哥就介绍几种获客渠道&#…

YOLOv5项目实战(5)— 算法模型优化和服务器部署

前言:Hello大家好,我是小哥谈。近期,作者所负责项目中的算法模型检测存在很多误报情况,为了减少这种误报情况,作者一直在不断优化算法模型。鉴于此,本节课就给大家详细介绍一下实际工作场景中如何去优化算法模型和进行部署,另外为了方便大家进行模型训练,作者在文章中提…

流量内存cpu使用率使用工具

类似360工具球的工具 我提供了夸克下载喜欢的朋友可以直接下载使用 我用夸克网盘分享了「TrafficMonitor」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。 链接:https://pan…

wpf devexpress 使用IDataErrorInfo实现input验证

此处下载源码 当form初始化显示,Register按钮应该启动和没有输入错误应该显示。如果用户点击注册按钮在特定的输入无效数据,form将显示输入错误和禁用的注册按钮。实现逻辑在标准的IDataErrorInfo接口。请查阅IDataErrorInfo接口(System.Com…
最新文章