雨痕 Go语言学习笔记-函数

函数定义

  1. 不⽀持 嵌套 (nested)、重载 (overload) 和 默认参数 (default parameter)。

    • ⽆需声明原型。

    • ⽀持不定⻓变参。

    • ⽀持多返回值。

    • ⽀持命名返回参数。

    • ⽀持匿名函数和闭包。
  2. 函数是第⼀类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读。
func test(fn func() int) int {
return fn()
}
type FormatFunc func(s string, x, y int) string // 定义函数类型。
func format(fn FormatFunc, s string, x, y int) string {
return fn(s, x, y)
}

变参

  1. 变参本质上就是 slice。只能有⼀个,且必须是最后⼀个。
func test(s string, n ...int) string {
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
  1. 使⽤ slice 对象做变参时,必须展开。
func main() {
s := []int{1, 2, 3}
println(test("sum: %d", s...))
}
  1. 命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。
  2. 命名返回参数可被同名局部变量遮蔽,此时需要显式返回。
func add(x, y int) (z int) {
{ // 不能在⼀个级别,引发 "z redeclared in this block" 错误。
var z = x + y
// return // Error: z is shadowed during return
return z // 必须显式返回。
}
}
  1. 命名返回参数允许 defer 延迟调⽤通过闭包读取和修改。
func add(x, y int) (z int) {
defer func() {
z += 100
}()
z = x + y
return
}
func main() {
println(add(1, 2)) // 输出: 103
}

匿名函数

  1. 匿名函数可赋值给变量,做为结构字段,或者在 channel ⾥传送
fn := func() { println("Hello, World!") }
fn()
// --- function collection ---
fns := [](func(x int) int){
func(x int) int { return x + 1 },
func(x int) int { return x + 2 },
}
println(fns[0](100))
// --- function as field ---
d := struct {
fn func() string
}{
fn: func() string { return "Hello, World!" },
}
println(d.fn())
// --- channel of function ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)())
  1. 闭包复制的是原对象指针,这就很容易解释延迟引⽤现象。
func test() func() {
x := 100
fmt.Printf("x (%p) = %d\n", &x, x)
return func() {
fmt.Printf("x (%p) = %d\n", &x, x)
}
}
func main() {
f := test()
f()
}
输出:
x (0x2101ef018) = 100
x (0x2101ef018) = 100
  1. 在汇编层⾯, test 实际返回的是 FuncVal 对象,其中包含了匿名函数地址、闭包对象指针。当调⽤匿名函数时,只需以某个寄存器传递该对象即可。
FuncVal { func_address, closure_var_pointer ... }

延迟调用

  1. 多个 defer 注册,按 FILO 次序执⾏。哪怕函数或某个延迟调⽤发⽣错误,这些调⽤依旧会被执⾏。
package main

import (
	"fmt"
)

func test(x int) {
	defer println("a")
	defer println("b")
	defer func() {
		fmt.Println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终⽌进程。
	}()
	defer println("c")
}

func main() {
	test(0)
}
# output
c
b
a
panic: runtime error: integer divide by zero
  1. 延迟调⽤参数在注册时求值或复制,可⽤指针或闭包 "延迟" 读取。这个例子考察defer顺序与赋值,闭包的引用复制
func test() {
	x, y := 10, 20
	defer func(i int) {
		println("defer:", i, y) // y 闭包引⽤
	}(x) // x 被复制
	x += 10
	y += 100
	println("x =", x, "y =", y)
}
输出:
x = 20 y = 120
defer: 10 120
  1. 滥⽤ defer 可能会导致性能问题,尤其是在⼀个 "⼤循环" ⾥

错误处理

  1. 延迟调⽤中引发的错误,可被后续延迟调⽤捕获,但仅最后⼀个错误可被捕获。
func test() {
	defer func() {//defer1
		fmt.Println(recover()) //捕获defer2中的panic
	}()
	defer func() {// defer2
		panic("defer panic") //defer2自己的panic代替捕获下层的panic
	}()
	panic("test panic") // panic,被上层defer2捕获
}
func main() {
	test()
}
输出:
defer panic
  1. 捕获函数 recover 只有在延迟调⽤内直接调⽤才会终⽌错误,否则总是返回 nil。任何未捕获的错误都会沿调⽤堆栈向外传递
func test() {
	defer fmt.Println(recover()) // 有效
	defer func() {
		fmt.Println(recover()) // 直接调用,有效,捕获 test panic
	}()
	defer func() {
		func() {
			fmt.Println("defer inner")
			recover() // ⽆效,没有直接调用
		}()
	}()
	panic("test panic")
}
func main() {
	test()
}
# output
defer inner
test panic
<nil>
  1. 如果需要保护代码⽚段,可将代码块重构成匿名函数,如此可确保后续代码被执⾏
func test(x, y int) {
	var z int
	func() {
		defer func() { // 这里的defer上下文是本匿名函数,在匿名函数return前返回
			if recover() != nil {
				z = 0
			}
		}()
		z = x / y
		return
	}()
	fmt.Println("x / y =", z)
}

func main() {
	test(4, 0)
	test(4, 2)
}
#output
x / y = 0
x / y = 2

总结

  1. 函数部分,函数别名,匿名函数,函数是第一公民,能够在channel里面传输
  2. 闭包,上下文中变量如何复制?引用复制
  3. 函数是重点,函数不定参数
  4. defer的执行顺序
  5. recover()捕获最新的panic
comments powered by Disqus