4

Golang参数值传递还是引用传递

 1 year ago
source link: https://www.leftpocket.cn/post/golang/pass_by_value/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Golang参数值传递还是引用传递

2022-07-02 约 1980 字 预计阅读 4 分钟 17 次阅读

先说结论,Go里面没有引用传递,Go语言是值传递。对这个有不同意见的,请往下看。

形参是实参的拷贝,改变形参的值并不会影响外部实参的值,是将实参的值拷贝到另外的内存地址中才修改。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

形参相当于是实参的“别名”,引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量,而指针可以改变其指向的对象)。对形参的操作其实就是对实参的操作,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

而Go语言中的一些让你觉得它是引用传递的原因,是因为Go语言有值类型引用类型,但是它们都是值传递

值类型

  • int、float、bool、array、sturct等

引用类型

  • slice,map,channel,interface,func等

  • 引用类型作为参数时,称为浅拷贝,形参改变,实参数跟随变化.因为传递的是地址,形参和实参都指向同一块地址

  • 值类型作为参数时,称为深拷贝,形参改变,实参不变,因为传递的是值的副本,形参会新开辟一块空间,与实参指向不同

  • 如果希望值类型数据在修改形参时实参跟随变化,可以把参数设置为指针类型

注意:很多博客说Go语言有值传递也有引用传递,是典型的误人子弟,希望大家能够分辨。

官网解释:https://go.dev/doc/faq#pass_by_value

When are function parameters passed by value?

As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to. (See a later section for a discussion of how this affects method receivers.)

Map and slice values behave like pointers: they are descriptors that contain pointers to the underlying map or slice data. Copying a map or slice value doesn’t copy the data it points to. Copying an interface value makes a copy of the thing stored in the interface value. If the interface value holds a struct, copying the interface value makes a copy of the struct. If the interface value holds a pointer, copying the interface value makes a copy of the pointer, but again not the data it points to.

我来翻译一下:

像 C 家族中的其他所有语言一样,Go 语言中的所有传递都是传值。
也就是说,函数接收到的永远都是参数的一个副本,就好像有一条将值赋值给参数的赋值语句一样。
例如,传递一个 int 值给一个函数,函数收到的是这个 int 值得副本,传递指针值,获得的是指针值得副本,而不是指针指向的数据。
(请参考 [later section] (https://golang.org/doc/faq#methods_on_values_or_pointers) 来了解这种方式对方法接收者的影响)

Map 和 Slice 的值表现和指针一样:它们是对内部映射或者切片数据的指针的描述符。
复制映射或者切片的值,不会复制它们指向的数据。复制接口的值,会产生一个接口值存储的数据的副本。
如果接口值存储的是一个结构体,复制接口值将产生一个结构体的副本。
如果接口值存储的是指针,复制接口值会产生一个指针的副本,而不是指针指向的数据的副本。
func main() {
	i := 1
	str := "old"

	stu := student{name: "ada", age: 1}

	modify(i, str, stu)
	fmt.Println(i, str, stu.age) //1 old 1
}

func modify(i int, str string, stu student) {
	i = 5
	str = "new"
	stu.age = 10
}

可以发现,在函数里面修改了值之后,函数外打印的值还是旧的值。

我们想要内部修改能在外面得到,怎么办呢?传指针。

func main() {
	i := 1
	str := "old"

	stu := &student{name: "ada", age: 1}

	modify(&i, &str, stu)
	fmt.Println(i, str, stu.age) //5 new 10
}

func modify(i *int, str *string, stu *student) {
	*i = 5
	*str = "new"
	stu.age = 10
}

注意这可不是引用传递,只是因为我们传入的是指针,指针本身是一份拷贝,但是对这个指针解引用之后,也就是指针所指向的具体地址,是不变的,所以函数内部的修改,在函数外面是知道的。

迷惑的Map

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

func main() {
	users := make(map[int]string)
	users[1] = "user1"

	fmt.Printf("before modify: user:%v\n", users[1])
	modify(users)
	fmt.Printf("after modify: user:%v\n", users[1])
}

func modify(u map[int]string) {
	u[1] = "user2"
}

//output
//before modify: user:user1
//after modify: user:user2

我们都知道,值传递是一份拷贝,里面的修改并不影响外面实参的值,那为什么map在函数内部的修改可以影响外部呢?

通过汇编语言可以看到,实际上底层调用的是 makemap 函数,主要做的工作就是初始化 hmap 结构体的各种字段

我们看一份源码:

func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
    //...
}

通过查看src/runtime/hashmap.go源代码发现,的确和我们猜测的一样,make函数返回的是一个hmap类型的指针*hmap。也就是说map===*hmap。 现在看func modify(p map)这样的函数,其实就等于func modify(p *hmap),相当于传递了一个指针进来。

而对于指针类型的参数来说,只是复制了指针本身,指针所指向的地址还是之前的地址。所以对map的修改是可以反馈到函数外部的。

chan类型

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

func makechan(t *chantype, size int64) *hchan {
    //...
}

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

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

pic

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK