我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go语言参数传递是传值还是传引用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go语言参数传递是传值还是传引用

目录

什么是传值(值传递)

什么是传引用(引用传递)

迷惑Map

chan类型

和map、chan都不一样的slice

小结

对于了解一门语言来说,会关心我们在函数调用的时候,参数到底是传的值,还是引用?

其实对于传值和传引用,是一个比较古老的话题,做研发的都有这个概念,但是可能不是非常清楚。对于我们做Go语言开发的来说,也想知道到底是什么传递。

那么我们先来看看什么是值传递,什么是引用传递。

什么是传值(值传递)

传值的意思是:函数传递的总是原来这个东西的一个副本,一副拷贝。比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。

对于int这类基础类型我们可以很好的理解,它们就是一个拷贝,但是指针呢?我们觉得可以通过它修改原来的值,怎么会是一个拷贝呢?下面我们看个例子。


func main() {
 i:=10
 ip:=&i
 fmt.Printf("原始指针的内存地址是:%p\n",&ip)
 modify(ip)
 fmt.Println("int值被修改了,新值为:",i)
}
 func modify(ip *int){
  fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)
  *ip=1
 }

我们运行,可以看到输入结果如下:

原始指针的内存地址是:0xc42000c028
函数里接收到的指针的内存地址是:0xc42000c038
int值被修改了,新值为: 1

首先我们要知道,任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指针的内存。

所以通过输出我们可以看到,这是一个指针的拷贝,因为存放这两个指针的内存地址是不同的,虽然指针的值相同,但是是两个不同的指针。

通过上面的图,可以更好的理解。 首先我们看到,我们声明了一个变量i,值为10,它的内存存放地址是0xc420018070,通过这个内存地址,我们可以找到变量i,这个内存地址也就是变量i的指针ip。

指针ip也是一个指针类型的变量,它也需要内存存放它,它的内存地址是多少呢?是0xc42000c028。 在我们传递指针变量ip给modify函数的时候,是该指针变量的拷贝,所以新拷贝的指针变量ip,它的内存地址已经变了,是新的0xc42000c038。

不管是0xc42000c028还是0xc42000c038,我们都可以称之为指针的指针,他们指向同一个指针0xc420018070,这个0xc420018070又指向变量i,这也就是为什么我们可以修改变量i的值。

什么是传引用(引用传递)

Go语言(Golang)是没有引用传递的,这里我不能使用Go举例子,但是可以通过说明描述。

以上面的例子为例,如果在modify函数里打印出来的内存地址是不变的,也是0xc42000c028,那么就是引用传递。

迷惑Map

了解清楚了传值和传引用,但是对于Map类型来说,可能觉得还是迷惑,一来我们可以通过方法修改它的内容,二来它没有明显的指针。


func main() {
 persons:=make(map[string]int)
 persons["张三"]=19
 mp:=&persons
 fmt.Printf("原始map的内存地址是:%p\n",mp)
 modify(persons)
 fmt.Println("map值被修改了,新值为:",persons)
}
 func modify(p map[string]int){
  fmt.Printf("函数里接收到map的内存地址是:%p\n",&p)
  p["张三"]=20
 }

运行打印输出:

原始map的内存地址是:0xc42000c028
函数里接收到map的内存地址是:0xc42000c038
map值被修改了,新值为: map[张三:20]

两个内存地址是不一样的,所以这又是一个值传递(值的拷贝),那么为什么我们可以修改Map的内容呢?先不急,我们先看一个自己实现的struct。


func main() {
 p:=Person{"张三"}
 fmt.Printf("原始Person的内存地址是:%p\n",&p)
 modify(p)
 fmt.Println(p)
}
type Person struct {
 Name string
}
 func modify(p Person) {
  fmt.Printf("函数里接收到Person的内存地址是:%p\n",&p)
  p.Name = "李四"
 }

运行打印输出:

原始Person的内存地址是:0xc4200721b0
函数里接收到Person的内存地址是:0xc4200721c0
{张三}

我们发现,我们自己定义的Person类型,在函数传参的时候也是值传递,但是它的值(Name字段)并没有被修改,我们想改成李四,发现最后的结果还是张三。

这也就是说,map类型和我们自己定义的struct类型是不一样的。我们尝试把modify函数的接收参数改为Person的指针。


func main() {
 p:=Person{"张三"}
 modify(&p)
 fmt.Println(p)
}
type Person struct {
 Name string
}
 func modify(p *Person) {
  p.Name = "李四"
 }

在运行查看输出,我们发现,这次被修改了。我们这里省略了内存地址的打印,因为我们上面int类型的例子已经证明了指针类型的参数也是值传递的。 指针类型可以修改,非指针类型不行,那么我们可以大胆的猜测,我们使用make函数创建的map是不是一个指针类型呢?看一下源代码:


// makemap implements a Go map creation make(map[k]v, hint)
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If bucket != nil, bucket can be used as the first bucket.
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
    //省略无关代码
}

通过查看class="lazy" data-src/runtime/hashmap.go源代码发现,的确和我们猜测的一样,make函数返回的是一个hmap类型的指针*hmap。也就是说map===*hmap。 现在看func modify(p map)这样的函数,其实就等于func modify(p *hmap),和我们前面第一节什么是值传递里举的func modify(ip *int)的例子一样,可以参考分析。

所以在这里,Go语言通过make函数,字面量的包装,为我们省去了指针的操作,让我们可以更容易的使用map。这里的map可以理解为引用类型,但是记住引用类型不是传引用。

chan类型

chan类型本质上和map类型是一样的,这里不做过多的介绍,参考下源代码:
func makechan(t *chantype, size int64) *hchan {
    //省略无关代码
}

chan也是一个引用类型,和map相差无几,make返回的是一个*hchan。

和map、chan都不一样的slice

slice和map、chan都不太一样的,一样的是,它也是引用类型,它也可以在函数中修改对应的内容。


func main() {
 ages:=[]int{6,6,6}
 fmt.Printf("原始slice的内存地址是%p\n",ages)
 modify(ages)
 fmt.Println(ages)
}
func modify(ages []int){
 fmt.Printf("函数里接收到slice的内存地址是%p\n",ages)
 ages[0]=1
}

运行打印结果,发现的确是被修改了,而且我们这里打印slice的内存地址是可以直接通过%p打印的,不用使用&取地址符转换。

这就可以证明make的slice也是一个指针了吗?不一定,也可能fmt.Printf把slice特殊处理了。


func (p *pp) fmtPointer(value reflect.Value, verb rune) {
 var u uintptr
 switch value.Kind() {
 case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
  u = value.Pointer()
 default:
  p.badVerb(verb)
  return
 }
 //省略部分代码
}

通过源代码发现,对于chan、map、slice等被当成指针处理,通过value.Pointer()获取对应的值的指针。


// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0.  If the slice is empty but non-nil the return value is non-zero.
func (v Value) Pointer() uintptr {
 // TODO: deprecate
 k := v.kind()
 switch k {
 //省略无关代码
 case Slice:
  return (*SliceHeader)(v.ptr).Data
 }
}

很明显了,当是slice类型的时候,返回是slice这个结构体里,字段Data第一个元素的地址。


type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}
type slice struct {
 array unsafe.Pointer
 len   int
 cap   int
}

所以我们通过%p打印的slice变量ages的地址其实就是内部存储数组元素的地址,slice是一种结构体+元素指针的混合类型,通过元素array(Data)的指针,可以达到修改slice里存储元素的目的。

所以修改类型的内容的办法有很多种,类型本身作为指针可以,类型里有指针类型的字段也可以。

单纯的从slice这个结构体看,我们可以通过modify修改存储元素的内容,但是永远修改不了len和cap,因为他们只是一个拷贝,如果要修改,那就要传递*slice作为参数才可以。


func main() {
 i:=19
 p:=Person{name:"张三",age:&i}
 fmt.Println(p)
 modify(p)
 fmt.Println(p)
}
type Person struct {
 name string
 age  *int
}
func (p Person) String() string{
 return "姓名为:" + p.name + ",年龄为:"+ strconv.Itoa(*p.age)
}
func modify(p Person){
 p.name = "李四"
 *p.age = 20
}

运行打印输出结果为:

姓名为:张三,年龄为:19
姓名为:张三,年龄为:20

通过这个Person和slice对比,就更好理解了,Person的name字段就类似于slice的len和cap字段,age字段类似于array字段。在传参为非指针类型的情况下,只能修改age字段,name字段无法修改。要修改name字段,就要把传参改为指针,比如:


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

这样name和age字段双双都被修改了。

所以slice类型也是引用类型。

小结

最终我们可以确认的是Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。

这里也要记住,引用类型和传引用是两个概念。

再记住,Go里只有传值(值传递)。

到此这篇关于Go语言参数传递是传值还是传引用的文章就介绍到这了,更多相关Go语言参数传递内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go语言参数传递是传值还是传引用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go语言参数传递是传值还是传引用

目录什么是传值(值传递)什么是传引用(引用传递)迷惑Mapchan类型和map、chan都不一样的slice小结对于了解一门语言来说,会关心我们在函数调用的时候,参数到底是传的值,还是引用? 其实对于传值和传引用,是一个比较古老的话题,做研
2022-06-07

Java是值传递还是引用传递

本篇内容主要讲解“Java是值传递还是引用传递”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java是值传递还是引用传递”吧!1.值类型通俗意义上来说,所谓的值类型指的就是 Java 中的 8
2023-06-16

Java语言是传值还是传引用

小编给大家分享一下Java语言是传值还是传引用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 简单类型是按值传递的  Java 方法的参数是简单类型的时候,是
2023-06-03

深入理解python中函数传递参数是值传递还是引用传递

目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用。Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典 或者列表)
2022-06-04

Java编程是值传递还是引用传递

这篇文章主要介绍“Java编程是值传递还是引用传递”,在日常操作中,相信很多人在Java编程是值传递还是引用传递问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java编程是值传递还是引用传递”的疑惑有所帮助!
2023-06-30

java支持值传递还是引用传递

本篇内容主要讲解“java支持值传递还是引用传递”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java支持值传递还是引用传递”吧!文章目的:验证Java语言到底是值传递还是引用传递以及Java参
2023-06-30

一文搞懂Golang 值传递还是引用传递

最多人犯迷糊的就是 slice、map、chan 等类型,都会认为是 “引用传递”,从而认为 Go 语言的 xxx 就是引用传递。正因为它们还引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据,这篇文章主要介绍了Golang 值传递还是引用传递,需要的朋友可以参考下
2023-01-11

php函数间的参数传递(值传递/引用传递)

函数间的参数传递包括值传递和引用传递,详细示例接下来为大家详细介绍下,感兴趣的朋友不要错过
2022-11-15

go语言中是否引用传递

这篇文章主要讲解了“go语言中是否引用传递”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言中是否引用传递”吧!没有。Go没有引用变量,所以Go语言里调用函数的时也没有引用传递。Go程序
2023-07-04

java的参数传递(只有值传递没有引用传递)

为什么改变引用变量的属性值却可以呢?请看下面的解答。java中的数据类型Java中数据类型分为两大类:基本类型和引用类型。相应的,变量也分这两种类型:基本类型和引用类型。基本类型的变量保存原始值,即它代表的值就是数值本身;而引用类型的变量保存的值是引用值,"引
java的参数传递(只有值传递没有引用传递)
2015-05-18

C++ 函数参数的传递方式详解:值传递和引用传递

c++++ 参数传递方式分为值传递和引用传递。值传递创建函数参数副本,不影响原变量;引用传递直接操作原变量。选择方式取决于需求:保护原变量使用值传递,修改原变量或提高效率使用引用传递。C++ 函数参数的传递方式详解:值传递和引用传递在 C
C++ 函数参数的传递方式详解:值传递和引用传递
2024-04-12

php数组是传值还是引用

php数组是传值。PHP数组传递是值传递;在调用函数时通过将PHP数组作为实参赋给形参,在函数中修改,并不会影响到数组本身,说明此过程中的传递是值传递,数组变量并非指向此数组本身的引用。
2023-05-14

C++ 函数参数传递值和传递引用的区别

c++++ 函数参数传递有值传递和引用传递两种方式:值传递:创建局部变量副本,对副本的修改不影响原始变量。引用传递:直接传递对原始变量的引用,对引用变量的修改反映在原始变量中。C++ 函数参数传递值和传递引用的区别传递值当函数参数通过值
C++ 函数参数传递值和传递引用的区别
2024-04-19

了解Go语言中参数是如何传递的

在 go 语言中,函数参数可以传递值或传递引用:传递值:复制参数的值,修改不影响原始变量。传递引用:使用指针,允许函数直接修改原始变量的值。在实际应用中,根据具体情况选择传递方式:按值传递避免意外更改原始变量。按引用传递用于在函数和调用者之
了解Go语言中参数是如何传递的
2024-04-03

Python函数值传递、引用传递、形式参数和实际参数的区别是什么

本篇内容主要讲解“Python函数值传递、引用传递、形式参数和实际参数的区别是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python函数值传递、引用传递、形式参数和实际参数的区别是什么”
2023-06-30

java经典问题:传值还是传引用(转)

java经典问题:传值还是传引用(转)[@more@]经典的问题,但却不容易弄懂,尤其对有c基础的java程序员来说,更容易引起混乱,这里我试图简单点描述。 “java函数是传值的,java函数传递的参数是对象的引用” 这两句话好像初听上去
2023-06-03

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录