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

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

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

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

什么是传值(值传递)

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

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

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

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

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

指针传递解释

通过上面的图,可以更好的理解。

首先我们看到,我们声明了一个变量,值为,它的内存存放地址是,通过这个内存地址,我们可以找到变量,这个内存地址也就是变量的指针。

指针也是一个指针类型的变量,它也需要内存存放它,它的内存地址是多少呢?是。

在我们传递指针变量给函数的时候,是该指针变量的拷贝,所以新拷贝的指针变量,它的内存地址已经变了,是新的。

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

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

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

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

迷惑Map

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

运行打印输出:

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

运行打印输出:

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

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

在运行查看输出,我们发现,这次被修改了。我们这里省略了内存地址的打印,因为我们上面类型的例子已经证明了指针类型的参数也是值传递的。

指针类型可以修改,非指针类型不行,那么我们可以大胆的猜测,我们使用函数创建的是不是一个指针类型呢?看一下源代码:

通过查看源代码发现,的确和我们猜测的一样,函数返回的是一个类型的指针。也就是说。

现在看这样的函数,其实就等于,和我们前面第一节什么是值传递里举的的例子一样,可以参考分析。

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

chan类型

类型本质上和类型是一样的,这里不做过多的介绍,参考下源代码:

也是一个引用类型,和相差无几,返回的是一个。

和map、chan都不一样的slice

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

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

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

通过源代码发现,对于、、等被当成指针处理,通过获取对应的值的指针。

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

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

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

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

运行打印输出结果为:

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

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

所以类型也是引用类型。

小结

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

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

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

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

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180224G0XV7300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券