Go语言之参数传递

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

修改参数

假设你定义了一个函数,并在函数里对参数进行修改,想让调用者可以通过参数获取你最新修改的值。

func main() {p:=person{name: "张三",age: 18}modifyPerson(p)fmt.Println("person name:",p.name,",age:",p.age)
}func modifyPerson(p person)  {p.name = "李四"p.age = 20
}type person struct {name stringage int
}

在这个示例中,我期望通过 modifyPerson 函数把参数 p 中的 name 修改为李四,把 age 修改为 20 。

代码没有错误,但是运行一下,你会看到如下打印输出:

person name: 张三 ,age: 18

怎么还是张三与 18 呢?我换成指针参数试试,如下所示:

modifyPerson(&p)
func modifyPerson(p *person)  {p.name = "李四"p.age = 20
}

这些代码用于满足指针参数的修改,把接收的参数改为指针参数,以及在调用 modifyPerson 函数时,通过&取地址符传递一个指针。

现在再运行程序,就可以看到期望的输出了,如下所示:

person name: 李四 ,age: 20

值类型

在 Go 语言中,person 是一个值类型,而 &p 获取的指针是 *person 类型的,即指针类型。

那么为什么值类型在参数传递中无法修改呢?这也要从内存讲起。

内存都有一个编号,称为内存地址。

所以要想修改内存中的数据,就要找到这个内存地址。

现在,我来对比值类型变量在函数内外的内存地址,如下所示:

func main() {p:=person{name: "张三",age: 18}fmt.Printf("main函数:p的内存地址为%p\n",&p)modifyPerson(p)fmt.Println("person name:",p.name,",age:",p.age)
}func modifyPerson(p person)  {fmt.Printf("modifyPerson函数:p的内存地址为%p\n",&p)p.name = "李四"p.age = 20
}

其中,我把原来的示例代码做了更改,分别打印出在 main 函数中变量 p 的内存地址,以及在 modifyPerson 函数中参数 p 的内存地址。

运行以上程序,可以看到如下结果:

main函数:p的内存地址为0xc0000a6020
modifyPerson函数:p的内存地址为0xc0000a6040
person name: 张三 ,age: 18

你会发现它们的内存地址都不一样,这就意味着,在 modifyPerson 函数中修改的参数 p 和 main 函数中的变量 p 不是同一个,这也是我们在 modifyPerson 函数中修改参数 p,但是在 main 函数中打印后发现并没有修改的原因。

导致这种结果的原因是 Go 语言中的函数传参都是值传递。

值传递指的是传递原来数据的一份拷贝,而不是原来的数据本身。

image-20240405204640653

以 modifyPerson 函数来说,在调用 modifyPerson 函数传递变量 p 的时候,Go 语言会拷贝一个 p 放在一个新的内存中,这样新的 p 的内存地址就和原来不一样了,但是里面的 name 和 age 是一样的,还是张三和 18。

这就是副本的意思,变量里的数据一样,但是存放的内存地址不一样。

除了 struct 外,还有浮点型、整型、字符串、布尔、数组,这些都是值类型。

指针类型

指针类型的变量保存的值就是数据对应的内存地址,所以在函数参数传递是传值的原则下,拷贝的值也是内存地址。

现在对以上示例稍做修改,修改后的代码如下:

func main() {p:=person{name: "张三",age: 18}fmt.Printf("main函数:p的内存地址为%p\n",&pmodifyPerson(&p)fmt.Println("person name:",p.name,",age:",p.age)
}func modifyPerson(p *person)  {fmt.Printf("modifyPerson函数:p的内存地址为%p\n",p)p.name = "李四"p.age = 20
}

运行这个示例,你会发现打印出的内存地址一致,并且数据也被修改成功了,如下所示:

main函数:p的内存地址为0xc0000a6020
modifyPerson函数:p的内存地址为0xc0000a6020
person name: 李四 ,age: 20

所以指针类型的参数是永远可以修改原数据的,因为在参数传递时,传递的是内存地址。

小提示:值传递的是指针,也是内存地址。通过内存地址可以找到原数据的那块内存,所以修改它也就等于修改了原数据。

引用类型

map

对于上面的例子,假如我不使用自定义的 person 结构体和指针,能不能用 map 达到修改的目的呢?

func main() {m:=make(map[string]int)m["飞雪无情"] = 18fmt.Println("飞雪无情的年龄为",m["飞雪无情"])modifyMap(m)fmt.Println("飞雪无情的年龄为",m["飞雪无情"])}func modifyMap(p map[string]int)  {p["飞雪无情"] =20}

我定义了一个 map[string]int 类型的变量 m,存储一个 Key 为飞雪无情、Value 为 18 的键值对,然后把这个变量 m 传递给函数 modifyMap。modifyMap 函数所做的事情就是把对应的值修改为 20。

现在运行这段代码,通过打印输出来看是否修改成功,结果如下所示:

飞雪无情的年龄为 18飞雪无情的年龄为 20

确实修改成功了。你是不是有不少疑惑?没有使用指针,只是用了 map 类型的参数,按照 Go 语言值传递的原则,modifyMap 函数中的 map 是一个副本,怎么会修改成功呢?

要想解答这个问题,就要从 make 这个 Go 语言内建的函数说起。在 Go 语言中,任何创建 map 的代码(不管是字面量还是 make 函数)最终调用的都是 runtime.makemap 函数。

小提示:用字面量或者 make 函数的方式创建 map,并转换成 makemap 函数的调用,这个转换是 Go 语言编译器自动帮我们做的。

从下面的代码可以看到,makemap 函数返回的是一个 *hmap 类型,也就是说返回的是一个指针,所以我们创建的 map 其实就是一个 *hmap。

// makemap implements Go map creation for make(map[k]v, hint).func makemap(t *maptype, hint int, h *hmap) *hmap{//省略无关代码}

因为 Go 语言的 map 类型本质上就是 *hmap,所以根据替换的原则,我刚刚定义的 modifyMap(p map) 函数其实就是 modifyMap(p *hmap)。这是不是和上一小节讲的指针类型的参数调用一样了?

这也是通过 map 类型的参数可以修改原始数据的原因,因为它本质上就是个指针。

为了进一步验证创建的 map 就是一个指针,我修改上述示例,打印 map 类型的变量和参数对应的内存地址,如下面的代码所示:

func main(){//省略其他没有修改的代码fmt.Printf("main函数:m的内存地址为%p\n",m)}func modifyMap(p map[string]int)  {fmt.Printf("modifyMap函数:p的内存地址为%p\n",p)//省略其他没有修改的代码}

例子中的两句打印代码是新增的,其他代码没有修改,这里就不再贴出来了。

运行修改后的程序,你可以看到如下输出:

飞雪无情的年龄为 18main函数:m的内存地址为0xc000060180modifyMap函数:p的内存地址为0xc000060180飞雪无情的年龄为 20

从输出结果可以看到,它们的内存地址一模一样,所以才可以修改原始数据,得到年龄是 20 的结果。而且我在打印指针的时候,直接使用的是变量 m 和 p,并没有用到取地址符 &,这是因为它们本来就是指针,所以就没有必要再使用 & 取地址了。

所以在这里,Go 语言通过 make 函数或字面量的包装为我们省去了指针的操作,让我们可以更容易地使用 map。其实就是语法糖,这是编程界的老传统了。

注意:这里的 map 可以理解为引用类型,但是它本质上是个指针,只是可以叫作引用类型而已。在参数传递时,它还是值传递,并不是其他编程语言中所谓的引用传递。

chan

它也可以理解为引用类型,而它本质上也是个指针。

通过下面的源代码可以看到,所创建的 chan 其实是个 *hchan,所以它在参数传递中也和 map 一样。

func makechan(t *chantype, size int64) *hchan {//省略无关代码}

严格来说,Go 语言没有引用类型,但是我们可以把 map、chan 称为引用类型,这样便于理解。

除了 map、chan 之外,Go 语言中的函数、接口、slice 切片都可以称为引用类型。

小提示:指针类型也可以理解为是一种引用类型。

类型的零值

在 Go 语言中,定义变量要么通过声明、要么通过 make 和 new 函数,不一样的是 make 和 new 函数属于显式声明并初始化。

如果我们声明的变量没有显式声明初始化,那么该变量的默认值就是对应类型的零值。

从下面的表格可以看到,可以称为引用类型的零值都是 nil。

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

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

相关文章

【已解决】Linux(Centos7)中yum过程域名无法解析问题

问题原因 Linux中yum过程域名无法解析问题,但是ping 域名时联通的(即DNS没问题),所以初步判断是镜像源的问题。 解决方法(Centos7) 1、备份/etc/yum.repos.d/CentOS-Base.repo 2、下载CentOS-Base.repo…

【Linux】Linux的基本使用

一.Linux的背景知识. 1.1什么是Linux Linux是一种开源的类Unix操作系统内核. 和Windows是" 并列 "的关系. 1.2Linux的发行版本. Linux 严格意义来说只是一个 “操作系统内核”.一个完整的操作系统 操作系统内核 配套的应用程序. 由于 Linux 是一个完全开源免费…

W29--python01-字典与字符串互转,列表和元组可以互转

# json字符串处理为字典 json_s [{"status":401,"msg":"用户名密码错误","st":true}] print(json_s) # 处理成字典? import json json_dict json.loads(json_s) print(json_dict) # 字典转json字符串 json_dict …

wsdl接口返回xml数据接收

WSDL(Web Services Description Language)是一种用于描述 Web 服务的 XML 格式。WSDL 接口可以返回 XML 数据,这是因为 WSDL 中定义了接口的输入参数和输出结果的数据类型。 在 WSDL 中,可以定义操作(operation),每个操作都有一个输入消息(input message)和一个输出消…

FPGA:二选一选择器

1、需求 使用XILINX的XC7A35TFFG484-2开发板,完成二选一选择器的设计。 2、分析 二选一选择器如下所示: 观察可知有三个输入端,一个输出端,其逻辑原理为:当sel为高电平时,outa,当sel为低电平…

Python面试题:详细讲解Python的多线程与多进程编程问题

在 Python 中,多线程和多进程编程是并发编程的两种主要方式,用于提高程序的执行效率和响应性。虽然它们都可以实现并发执行,但它们的工作原理和适用场景有所不同。以下是对 Python 多线程和多进程编程的详细讲解,包括它们的工作原…

高级PER语言编程:优化和性能提升

高级PER语言编程:优化和性能提升 作为一名资深的PER语言技术工程师,我很高兴能为你提供关于高级PER语言编程的详细指南,特别是在优化和性能提升方面。本文将深入探讨各种优化技术和性能提升策略,并通过示例代码来展示如何在实际项…

十二、数组(2)

1.冒泡排序数组(升序) 冒泡排序:将一个整型数组排序(升序) 例: 10 9 8 7 6 5 4 3 2 1 9 10 8 7 6 …

Nginx系列-4 proxy_pass使用和路径拼接问题

1.proxy_pass使用 proxy_pass指令用于请求的转发,请客户端请求转发至第三方服务器;如下所示: location /query {proxy_pass http://www.baidu.com; }上述案例将以/query开头的url请求转发至http://www.baidu.com. proxy_pass用法较为简单,但…

第十四届蓝桥杯省赛C++C组C题【三国游戏】题解(AC)

解题思路 由于三种国家都有获胜的可能,所以我们需要分别枚举 X , Y , Z X,Y,Z X,Y,Z 获胜的情况。 设 X X X 获胜,那么对于第 i i i 个事件的贡献为 a [ i ] − ( b [ i ] c [ i ] ) a[i]-(b[i]c[i]) a[i]−(b[i]c[i]),根据贪心的策略…

【银河麒麟服务器操作系统】java进程oom现象分析及处理建议

了解银河麒麟操作系统更多全新产品,请点击访问麒麟软件产品专区:https://product.kylinos.cn 现象描述 某服务器系统升级内核至4.19.90-25.22.v2101版本后仍会触发oom导致java进程被kill。 现象分析 oom现象分析 系统messages日志分析,故…

人工智能前沿讲座——融合知识的自然语言处理

目录 引言 NLP的发展与现状 发展 机器翻译 机器翻译知识记忆(高频词汇记录) 现状 我们的尝试 融入外部知识库 融合语言先验知识 融合语篇知识 融合人类行为知识 引言 自然语言处理没有大家都认可的一个定义。 研究人与人交际中以及在人与计算…

配置文件格式 JSON 快速上手

文章目录 1.语法2.实例3.解析参考文献 JSON(JavaScript Object Notation) 是轻量级的文本数据交换格式,独立于语言,具有自我描述性。JSON 类似于 XML,但比 XML 更小、更快,更易解析。 1.语法 JSON 语法是 …

数据编织 Data Fabric:解决“数据孤岛”的新思路

一个不争的事实是,企业内部数据孤岛的形成,根因在于业务发展的复杂性与技术迭代的快速性导致。具体而言,随着企业业务快速增长,如新生产线的引入或外部公司的并购,这些活动往往伴随着新系统上线与独立数据体系的融入&a…

【safari】react在safari浏览器中,遇到异步时间差的问题,导致状态没有及时更新到state,引起传参错误。如何解决

在safari浏览器中,可能会遇到异步时间差的问题,导致状态没有及时更新到state,引起传参错误。 PS:由于useState是一个普通的函数, 定义为() > void;因此此处不能用await/async替代setTimeout,只能用在返…

网络安全行业最大的敌人是自己

在危机四伏的数字丛林时代,网络安全行业需要跳出资本和市场的博弈陷阱,通过拯救自己来拯救所有人。当然,政府和资本也应该意识到这一点。 在当今这个数字化时代,网络安全的重要性与日俱增。然而,尽管政府和企业不断强调…

【vue+el-table】实现表尾合计行分两行显示,一行显示勾选项之和,一行显示合计,已实现,具体思路解析

效果图: 思路解析: 首先进行了el-table列表的组件封装,很多参数是传进来的。如果是普通的列表,相关参数直接定义就行 1、使用el-table的summary-method处理表尾行 (1)定义summaryIndex用于指定合计在哪一列…

Linux下载网络文档

1. 使用wget 1.1 安装wget sudo apt install wget1.2 下载网页或者文件 wget URL1.3 下载并重命名 wget -O filename URL1.4 下载文件夹 wget -r ftp://server-address.com/directory1.5 下载整个网站 wget -m --convert-links --page-requisites website_address2. 使用…

vue学习笔记(十一)——开发心得(axios的封装、promise细节、vue-router开发中的使用)

1. axios的网络请求的封装 1.1 为什么要封装api? 代码分层,便于以后的修改,无需触碰逻辑页面 目标: 网络请求,不散落在各个逻辑页面里,封装起来方便以后修改 1.2 封装api步骤 ① 在项目 src 下新建目录 utlis &am…

浅谈大模型领域内数据微调的一些个人理解

近期由于连续做了两个大模型相关的项目尝试,发现一些现象,在查阅了多篇论文及圈内大佬的点评之后,个人对大模型领域内数据微调有了更深入的理解,今天简单交流下,个人的一些理解,欢迎资深大佬对其中的不足之…