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

Go语言参数传递是传值还是传引用?首先我们先了解下两个概念:

  • 传值(Pass By Value) 函数传递的总是原来这个东西的一个副本,一副拷贝。修改拷贝不会对原对象产生影响。
  • 传引用(Pass By Reference) 函数传递的总是原来这个东西的指针引用,修改拷贝改变原对象的值。

通过上面的描述,很容易让人误认为Go语言参数传递是有传值和传引用的,恰恰相反,Go语言的参数传递是没有引用的,只有传值。如果现在你脑海里有跟我一样的迷惑请往下看个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
i := 10
fmt.Printf("原始值的内存地址是:%p\n", &i)
modifyInt(i)
fmt.Println("i的新值为:", i)
}

func modifyInt(i int) {
fmt.Printf("函数里接收到的地址是:%p\n", &i)
i = 2
}

运行这段代码,输出结果:

1
2
3
原始值的内存地址是:0xc000014088
函数里接收到的地址是:0xc000014090
i的新值为: 10

这里的地址每次运行结果都会不一样,但是我们可以看出来,此时函数内的i是一份拷贝,有了一个新地址。修改他并不会影响原值。继续修改下代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
i := 10
fmt.Printf("原始值的内存地址是:%p\n", &i)
modifyInt(&i)
fmt.Println("i的新值为:", i)
}

func modifyInt(i *int) {
fmt.Printf("函数里接收到的地址是:%p\n", &i)
*i = 2
}

运行代码,输出结果:

1
2
3
原始值的内存地址是:0xc000082008
函数里接收到的地址是:0xc000090010
i的新值为: 2

值改变了!不是只传值吗, 怎么现在又可以传引用?如果不幸入坑,继续往下看,首先我们来看函数参数i是什么类型,指针!那么&i,没错就是指针的指针,我们打印一下原始指针的地址.。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
i := 10
fmt.Printf("原始值的内存地址是:%p\n", &i)
ip := &i
fmt.Printf("原始指针的内存地址是:%p\n", &ip)
modifyInt(ip)
fmt.Println("i的新值为:", i)
}

func modifyInt(i *int) {
fmt.Printf("函数里接收到的指针地址是:%p\n", &i)
*i = 2
}

输出结果:

1
2
3
4
原始值的内存地址是:0xc000080008
原始指针的内存地址是:0xc000090010
函数里接收到的指针地址是:0xc00000e018
i的新值为: 2

可以看出函数中拷贝了一份新的指针,用图表示如下:

go指针

验证代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"unsafe"
)

func main() {
i := 10
fmt.Printf("原始值的内存地址是:%p\n", &i)
ip := &i
fmt.Printf("原始指针的内存地址是:%p\n", &ip)
fmt.Printf("原始指针实际值是:%v\n", unsafe.Pointer(ip))
modifyInt(ip)
fmt.Println("i的新值为:", i)
}

func modifyInt(i *int) {
fmt.Printf("函数里接收到的指针地址是:%p\n", &i)
fmt.Printf("函数里实际指针值是:%v\n", unsafe.Pointer(i))
*i = 2
}

输出结果:

1
2
3
4
5
6
原始值的内存地址是:0xc000088000
原始指针的内存地址是:0xc000082018
原始指针实际值是:0xc000088000
函数里接收到的指针地址是:0xc000094000
函数里实际指针值是:0xc000088000
i的新值为: 2

可以看出函数里和函数外的指针的值存储的是原始值的指针地址。

结论:Go语言参数传递是传值。需要注意的是Go中的mapchannelslice都隐含一项指针,传值也是把指针传进去了,所以也是会修改到原值。