爱上开源之golang入门至实战第四章函数(Func)(三)
Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本 的值进行更改,但不会影响到原来的变量,比如 Function(arg1) 。 如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符 号,比如 &variable)传递给函数,这就是按引用传递,比如 Function(&arg1) ,此时传递给函数的是一个指针。 如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们 可以通过这个指针的值来修改这个值所指向的地址上的值。(指针也是变量类型,有自己的地址和值,通常 指针的值指向一个变量的地址。所以,按引用传递也是按值传递。)

- func exchange(a, b int) {
- var temp int
- fmt.Printf("Before exchanged: a=%d, b=%d\n", a, b)
- temp = b
- b = a
- a = temp
- fmt.Printf("After exchanged: a=%d, b=%d\n", a, b)
- }
上方定义了一个exchange的函数,函数的传入参数是a,b两个int类型的数字, 函数交换两个参数的数值,这个函数的运行结果就可以充分的演示出按值传递的效果, 参数的参数都是副本, 所以执行结束以后,原有的a,b两个原值在函数体里都是在对副本进行的操作,函数体执行结束,a,b继续使用原值,所以执行完exchange以后,a,b值不会发生变化。
-
- a, b := 1, 2
- fmt.Printf("Before Func: a=%d, b=%d\n", a, b)
- exchange(a, b)
- fmt.Printf("After Func: a=%d, b=%d\n", a, b)
-
- ==== OUTPUT ====
- Before Func: a=1, b=2
- Before exchanged: a=1, b=2
- After exchanged: a=2, b=1
- After Func: a=1, b=2
结果输出可以看到,在执行之前a=1, b=2; 当进入函数体,两个原值都是用副本, 所以输出 a=1, b=2; 交换两个副本值,输出a=2, b=1;函数体结束,副本销毁,a,b使用原值,所以输出原值a=1, b=2;
我们可以改造一下exchange的函数体,来看看传入的两个参数的指针地址,是否和原来的一样。
- func exchange2(a, b int) {
- var temp int
- fmt.Printf("Before exchanged: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
- temp = b
- b = a
- a = temp
- fmt.Printf("After exchanged: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
- }
- a, b := 1, 2
- fmt.Printf("Before Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
- exchange2(a, b)
- fmt.Printf("After Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
-
- ==== OUTPUT ====
- Before Func: a[0xc0002f4e08]=1, b[0xc0002f4e10]=2
- Before exchanged: a[0xc0002f4e18]=1, b[0xc0002f4e20]=2
- After exchanged: a[0xc0002f4e18]=2, b[0xc0002f4e20]=1
- After Func: a[0xc0002f4e08]=1, b[0xc0002f4e10]=2
通过这里的代码测试,我们可以看到在函数调用前,和函数体里,函数体使用的是副本,指针地址不是原地址。 在函数体执行完成以后,变量a,b的地址又重新恢复成以前的指针地址,这就是我们所说的按值传递, 参数传递的是值的副本(拷贝对象)。
如果我们需要在上述的方法,确实一定要实现交换值的目标,我们可以通过传指针或者返回值覆盖原值的方式来实现; 代码如下:
传递指针
- func exchange3(a, b *int) {
- var temp int
- fmt.Printf("Before exchanged: a[%p]=%d, b[%p]=%d\n", a, *a, b, *b)
- temp = *b
- *b = *a // 不能直接b = a 为什么?
- *a = temp
- fmt.Printf("After exchanged: a[%p]=%d, b[%p]=%d\n", a, *a, b, *b)
- }
- a, b := 1, 2
- fmt.Printf("Before Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
- exchange3(&a, &b)
- fmt.Printf("After Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
-
- ==== OUTPUT ====
- Before Func: a[0xc0002ece18]=1, b[0xc0002ece20]=2
- Before exchanged: a[0xc0002ece18]=1, b[0xc0002ece20]=2
- After exchanged: a[0xc0002ece18]=2, b[0xc0002ece20]=1
- After Func: a[0xc0002ece18]=2, b[0xc0002ece20]=1
这段代码要细品;
品味1: 如何完成值交换的? // 不能直接b = a 为什么?
品味2:参数都换成了int类型的指针,注意点,指针的地址都是原来的地址?
返回值覆盖原值
- func exchange4(a, b int) (int, int) {
- var temp int
- fmt.Printf("Before exchanged: a=%d, b=%d\n", a, b)
- temp = b
- b = a
- a = temp
- fmt.Printf("After exchanged: a=%d, b=%d\n", a, b)
- return a, b
- }
- a, b := 1, 2
- fmt.Printf("Before Func: a=%d, b=%d\n", a, b)
- a, b = exchange4(a, b)
- fmt.Printf("After Func: a=%d, b=%d\n", a, b)
-
- ==== OUTPUT ====
- Before Func: a=1, b=2
- Before exchanged: a=1, b=2
- After exchanged: a=2, b=1
- After Func: a=2, b=1
几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。 调用都是更高效的调用,从基本上来说,至少传递指针的方式,只是一个32位或者64位值的复制,而值传递的方式,就是要对传递的对象进行值拷贝。基本数据类型差异可能不明显,但是对于结构体,或者是数据量大的数组而言,就有很大的差异了。
在函数调用时,像切片(slice)、字典(map)、接口(interface)、函数(Func)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)。
- func exchange5(a []int) {
- var temp int
- fmt.Printf("Before exchanged: Array=%v\n", a)
- temp = a[1]
- a[1] = a[0]
- a[0] = temp
- fmt.Printf("After exchanged: Array=%v\n", a)
- }
- a := make([]int, 0)
- a = append(a, 1, 2)
-
- fmt.Printf("Before Func: Array=%v\n", a)
- exchange5(a)
- fmt.Printf("After Func: Array=%v\n", a)
-
- ==== OUTPUT ====
- Before Func: Array=[1 2]
- Before exchanged: Array=[1 2]
- After exchanged: Array=[2 1]
- After Func: Array=[2 1]

变量直接存储值,内存通常在栈中分配

变量存储的是一个地址,这个地址对应的空间才真正存储数据(值); 内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收