Go 函数

1、参数

在 Go 中,不管是指针、引用类型,还是其他类型参数,都是值拷贝传递。区别无非是拷贝目标对象,还是拷贝指针而已。在函数面前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存。
表面上看,指针参数的性能要更好一些,但实际上得具体分析。被复制的指针会延长目标对象的生命周期,还可能会导致它被分配到堆上,那么其性能消耗就得加上堆内存分配和垃圾回收的成本。
其实在栈上分配小对象只需要很少的指令即可完成,远比运行时进行堆内存分配要快得多。另外,并发编程也提倡尽可能使用不可变对象(只读或复制),这可消除数据同步等麻烦。当然,如果复制成本很高,或需要修改原对象状态,自然使用指针更好。

package main

func test(p *int)  {
	go func() {
		println(p)
	}()

}

func main()  {
	x := 100
	p := &x
	test(p)

}

可执行:go build -gcflags “-m”

2、变参

变参本质上就是一个切片。只能接收一到多个同类型参数,且必须放在列表尾部。
既然变参是切片,那么参数复制的仅是切片自身,并不包括底层数组,也因此可修改原数据。如果需要,可用内置函数 copy 复制底层数据。

package main

import "fmt"

func test(a ...int)  {
	for i := range a {
		a[i] += 100
	}

}

func main()  {
	a := []int{10, 20, 30}
	test(a...)
	fmt.Println(a)

}

输出结果: [110 120 130]

3、闭包

闭包是其在词法上下文中引用了自由变量的函数,或者说是函数和其引用环境的组合体。

package main

func test(x int) func() {
	return func() {
		println(x)
	}

}

func main()  {
	f := test(123)
	f()

}

输出结果: 123
就这段代码而言,test 返回的匿名函数会引用上下文环境变量 x。当该函数在 main 中执行时,它依然可正确读取 x 的值,这种现象称作闭包。

package main

func test(x int) func() {
	println(&x)

	return func() {
		println(&x, x)
	}

}

func main()  {
	f := test(0x100)
	f()

}

输出结果:
0xc000018070
0xc000018070 256
通过输出指针,我们注意到闭包直接引用了原环境变量。闭包是函数和引用环境的组合体。
正因为闭包通过指针引用环境变量,那么可能会导致其生命周期延长,甚至被分配到堆内存。另外,还有所谓“延迟求值”的特性。

package main

func test() []func() {
	var s []func()
	for i := 0; i < 2; i++ {
		s = append(s, func() {
			println(&i, i)
		})
	}
	return s
}

func main()  {
	for _, f := range test() {
		f()
	}

}

输出:
0xc000018070 2
0xc000018070 2
针对这个结果,由于 for 循环复用局部变量i,那么每次添加的匿名函数引用的自然是同一变量。添加操作仅仅是将匿名函数放入列表,并未执行。因此,当 main 执行这些函数时,它们读取的是函数变量 i 最后一次循环时的值,2。
解决方法是每次用不同的环境变量或传参复制,让各自闭包环境各不相同。

package main

func test() []func() {
	var s []func()
	for i := 0; i < 2; i++ {
		x := i
		s = append(s, func() {
			println(&x, x)	
		})
	}
	return s
}

func main()  {
	for _, f := range test() {
		f()
	}

}

输出:
0xc000018070 0
0xc000018078 1
多个匿名函数引用同一环境变量,也会让事情变得更加复杂。任何的修改行为都会影响其他函数的取值,在并发模式下可能需要做同步处理。

package main

func test(x int) (func(), func()) {
	return func() {
		println(x)
		x += 10
	}, func() {
		println(x)
	}
}

func main()  {
	a, b := test(100)
	a()
	b()

}

输出:
100
110

4、延迟调用

多个延迟注册按 FIFO 次序执行。

package main

func main()  {
	defer println("a")
	defer println("b")

}

输出:
b
a

编译器通过插入额外指令来实现延迟调用执行,而 return 和 panic 语句都会终止当前函数流程,引发延迟调用。另外,return 语句不是 ret 汇编指令,它会先更新返回值。

package main

func test() (z int) {
	defer func() {
		println("defer:", z)
		z += 100
	}()

	return 100
}

func main()  {
	println("test:", test())
}

输出:
defer: 100
test: 200
相比直接用 CALL 汇编指令调用函数,延迟调用则须花费更大代价。这其中包括注册、调用等操作,还有额外的缓存开销。
go test -v -bench=.
输出:
goos: darwin
goarch: amd64
BenchmarkCall-8 100000000 14.4 ns/op
BenchmarkDefer-8 30000000 43.4 ns/op

5、错误处理

在代码中,若连续调用 panic,仅最后一个会被 recover 捕获。

package main

import "log"

func main()  {
	i := 0
	defer func() {
		for  {
			i++
			if i >= 5 {
				break
			}
			if err := recover(); err != nil {
				log.Println(err)
			} else {
				log.Println("fatal")
			}

		}
	}()

	defer func() {
		panic("hello world")
	}()

	panic("hi~~, world")

}

执行结果为:
2019/09/12 08:38:08 hello world
2019/09/12 08:38:08 fatal
2019/09/12 08:38:08 fatal
2019/09/12 08:38:08 fatal

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值