一、函数参数传递方式

函数参数传递有2种方式:值传递和引用传递。
值传递是指调用函数时将参数值复制一份到函数,如果对函数参数进行修改,影响不到实际参数。
引用传递是指调用函数时将参数的指针传递到函数中,函数中对参数的修改,将影响到实际参数。
不同编程语言的函数参数传递方式不尽相同,例如C语言中的int、float等参数类型采用值传递的方式,而数组类型默认为引用传递,C++中的引用传递显而易见,其类型系统直接包含了引用类型。

二、Go语言中的函数传递方式

那么Go语言何种参数传递方式呢?开门见山,直接上官方说法“Go语言函数传递方式都是值传递”。int64、string等类型采用值传递比较好理解,但是为什么说slice、interface、map和channel类型也是值传递呢?先看个例子1:

package main

import "fmt"

func fun0(slice0 []int) {
	slice0 = append(slice0, 2)
	fmt.Println("func0 : ", slice0)
}

func main() {
	slice0 := []int{1}
	fun0(slice0)
	fmt.Println("main : ", slice0)
}

打印结果:

func0 :  [1 2]
main :  [1]

分析:fun0中slice0追加元素并没有影响到main函数中的slice0,可见slice采用的是值传递方式。

我们再看另外一个例子2:

package main

import "fmt"

func fun0(map0 map[string]int) {
	map0["fun0"] = 1
	fmt.Println("func0 : ", map0)
}

func main() {
	map0 := map[string]int{"main":1}
	fun0(map0)
	fmt.Println("main : ", map0)
}

打印结果:

func0 :  map[fun0:1 main:1]
main :  map[fun0:1 main:1]

分析:func0中map0添加新元素,但是确实是影响到了main函数中的map0,看着好像map采用的是引用传递方式,实则不然。

三、为什么说map、slice等类型采用值传递?

看似slice类型为值传递,map类型为引用传递,而实际上却都是值传递。为什么会出现上述两种不同的情况呢?为了方便说明这种现象,本文根据slice、map、interface和channel等类型的实现方式做一些抽象,将这些类型统一表示为1个结构体,其中包含一个或多个指针,指向数据真正的存储,如下图所示。
在这里插入图片描述

有了上述抽象,我们再看下,当slice、map、interface和channel变量作为参数传递到另外一个函数时,会出现什么样的情况。以第二节中的2个例子来说明,入参和形参的存储结构如下图所示。
在这里插入图片描述

可以看出,参数传递前后data数据并没有发生变化,但struct结构却是不同的,被调函数中生成了一份struct副本。
3.1 Slice的函数传递方式
在例子1中func0中调用append追加了1个元素,有2种可能的情况:1、数组容量不足,append导致数组扩展;2、数组容量充足,append只是在原来的data存储增加了1个元素。分别两者对应的存储如下图所示。
在这里插入图片描述

第一种情况,struct和data都和原来的入参分道扬镳,fun0中的修改当然不会什么影响到main函数。
在这里插入图片描述

第二种情况,slice在参数传递前后共用一段存储data,但是需要注意的是struct结构中的len前后不同,因此main函数中同样不能感知到有新追加的元素。
这就解释了为什么fun0中slice追加新元素,在main函数中无影响。
3.2 Map中的函数传递方式
在例子2中,func0调用map0[“fun0”]=2,调用前后的存储结构,如下图所示。
在这里插入图片描述

可以看出fun0中追加元素同样在main中也能感知到。那能否据此说,Go语言中的map采用的是引用传递的方式呢?其实是不对的,看个例子3:

package main

import "fmt"

func fun0(map0 map[string]int) {
	map0 = map[string]int {
		"fun0":1,
	}
	fmt.Println("func0 : ", map0)
}

func main() {
	map0 := map[string]int{"main":1}
	fun0(map0)
	fmt.Println("main : ", map0)
}

打印结果:

func0 :  map[fun0:1]
main :  map[main:1]

分析:func0中重新赋值map0,并没有影响到main函数,所以map的传递其实也是值传递,从调用前后的存储结构图,也可以一窥究竟。
在这里插入图片描述

那有没有什么办法不通过返回值赋值的方式,使得被调函数的参数改变能够影响到主调函数呢?当然是有办法的,就是直接传递变量的指针例如代码4。不过Go语言并不建议采用这样方式来实现引用传递。

package main

import "fmt"

func fun0(map0 *map[string]int) {
	*map0 = map[string]int {
		"fun0":1,
	}
	fmt.Println("func0 : ", *map0)
}

func main() {
	map0 := map[string]int{"main":1}
	fun0(&map0)
	fmt.Println("main : ", map0)
}

打印结果:

func0 :  map[fun0:1]
main :  map[fun0:1]