Go语言引用传递与值传递

Go语言引用传递与值传递

GO中只有值传递,没有引用传递

所谓值传递,就是实参通过拷贝将自身内容传递给形参。也就是将传递的内容拷贝一份,给函数。所以函数外和函数里对这个参数地址求值,应该是不一样的。

证明如下:

func main() {
	slice := []int{0,1,2,3}
	m := make(map[string]string)
	m["A"] = "a"
	var i *int
	a := 123
	i = &a
	fmt.Printf("[main pointer] %p\n", &i)
	fmt.Printf("[main map] %p\n", &m)
	fmt.Printf("[main slice] %p\n", &slice)
	get(slice, m, i)
}

func get(s []int, m map[string]string, i *int)  {
	fmt.Printf("[get pointer] %p\n", &i)
	fmt.Printf("[get map] %p\n", &m)
	fmt.Printf("[get slice] %p\n", &s)
}

// output
[main pointer] 0xc0000ae020
[main map] 0xc0000ae018
[main slice] 0xc0000a6020
[get pointer] 0xc0000ae038
[get map] 0xc0000ae030
[get slice] 0xc0000a6040

可以发现,slice、map、指针在传递过程中,地址都发生了变化。这说明传递的是一份拷贝。

但是我们又发现,在函数里修改slice、map,函数外的值也会改变,这是为什么呢?

// main
get(slice, m, i)
fmt.Println(slice)
fmt.Println(m)
fmt.Println(*i)

// get
s[1] = 9999
m["A"] = "QQQQQ"
a := 999
i = &a

// output
[0 9999 2 3] // 修改之前为 []int{0,1,2,3}
map[A:QQQQQ] // 修改之前为 m["A"] = "a"
123          // 修改之前为 *i = 123

这就必须说到slice、map的存储结构了。以slice为例

slice 存储结构

slice结构主要包括3个部分:data、len、cap。结构中的len和cap分别指示元素数量和容量。append使得len变大,但如果append之后的元素个数大于cap,会引发扩容机制。此时,会重新创建一个容量适合的底层数组。data为指针,指向底层数组。

所以slice本质上指的就是这个结构(data+len+cap,不包括底层的数组)。在参数传递过程中,这个结构拷贝了一份,data指向的还是原来的底层数组。当我们对slice中的元素进行修改时,还是会通过拷贝之后的data,直接对底层数组进行修改。

go 中,slice、map、channel都是引用类型,所以都会有如上的特性。

由于slice在扩容的过程中,会重新创建底层数组,data指向新的数组。那么,我们可以推测,在get函数中如果能触发扩容,那么修改新的底层数组,并不会对main中的slice造成影响。是否这样呢?

func main() {
	slice := []int{0,1,2,3}
	fmt.Printf("[main slice] %p\n", &slice)
	get(slice)
	fmt.Printf("[new slice] %v\n",slice)
}

func get(s []int)  {
	fmt.Printf("[get slice] %p\n", &s)
	s = append(s, 9999)
}

// output
[main slice] 0xc0000a6020
[get slice] 0xc0000a6040
[new slice] [0 1 2 3]

果然如此!那么就很清楚了。

总结

1、go中函数传递都是值传递

2、slice、map、channel都是引用类型,即便是值传递,结构内部还是指向原来的引用对象,所以函数体内可以直接修改元素。

3、如果slice触发扩容,data会指向新的底层数组,而不指向外部的底层数组了。所以之后再修改slice,不会对外部的slice造成影响。

posted @ 2021-04-20 21:45  TR_Goldfish  阅读(1186)  评论(0编辑  收藏  举报