函数

函数 #

参数传递 #

函数的参数传递有两种方式:

  • 值传递:当传一个参数值到被调用的函数里面时,实际上是传了这个值的副本,被调用方和调用方两者持有不相关的两份数据。
  • 引用传递:当传一个参数值到被调用的函数里面时,实际是传了参数的指针,被调用方和调用方两者持有相同的数据,任意一方做出的修改都会影响另一方。

Go 使用的是值传递,不管参数是基本类型,结构体还是指针,都会对传递的参数进行拷贝,区别无非是拷贝的目标对象还是拷贝指针。拷贝指针,也就是会同时出现两个指针指向原有的内存空间。

package main

import "fmt"

type foo struct {
	i int
}

func printFunc(a foo, b, c *foo) {
	a.i = 31
	b.i = 41
	c = &foo{i: 60}
	fmt.Printf("print function - a=(%d, %p) b=(%v, %p) c=(%v, %p)\n", a, &a, b, &b, c, &c)
}

func main() {
	a := foo{i: 30}
	b := &foo{i: 40}
	c := &foo{i: 50}
	fmt.Printf("before calling - a=(%d, %p) b=(%v, %p) c=(%v, %p)\n", a, &a, b, &b, c, &c)
	printFunc(a, b, c)
	fmt.Printf("after calling  - a=(%d, %p) b=(%v, %p) c=(%v, %p)\n", a, &a, b, &b, c, &c)
}

运行后输出:

before calling - a=({30}, 0xc00000a0d8) b=(&{40}, 0xc00004c020) c=(&{50}, 0xc00004c028)
print function - a=({31}, 0xc00000a120) b=(&{41}, 0xc00004c038) c=(&{60}, 0xc00004c040)
after calling  - a=({30}, 0xc00000a0d8) b=(&{41}, 0xc00004c020) c=(&{50}, 0xc00004c028)
  • a 传入函数的只是副本,函数内的修改不会影响到调用方。
  • b 传入函数的是指针的副本,但是两个指针指向同一片内存空间,修改后会影响到调用方。
  • c 传入函数的是指针的副本,但是函数内的 c = &foo{i: 60} 将这个指针副本指向了另一片内存空间,所以不会再影响调用方。

传值还是传指针? #

表面上看,指针参数性能会更好,但是要注意被复制的指针会延长目标对象的生命周期,还可能导致它被分配到堆上,其性能消耗要加上堆内存分配和垃圾回收的成本。

在栈上复制小对象,要比堆上分配内存要快的多。如果复制成本高,或者需要修改原对象,使用指针更好