函数不支持嵌套、重载和默认参数。支持:
- 无需声明原型
- 支持不定长变参
- 支持多返回值
- 支持命名返回参数
- 支持匿名函数和闭包
函数可作为参数传递,通常将复杂签名定义为函数类型,以便于阅读。比如:
type FormatFunc func(s string, x, y int) string
有返回值的函数,必须有明确的终止语句,否则引发编译错误。
变参
变参本质上就是slice
。只能有一个,并且是最后一个。slice
作为入参时,必须展开。
func test(nums ...int) {
for _, n := range nums {
fmt.Printf("%d, ", n) // 1, 2, 3, 4,
}
}
func main() {
test(1, 2, 3, 4)
s := []int{1, 2, 3, 4}
test(s...)
}
返回值
不能用容器接收返回值。只能用多个变量或者_
忽略。
匿名函数
匿名函数可赋值给变量,作为结构字段,或者在channel
里传送。
// --- 赋值给变量 ---
fn := func() { println("Hello, World!") }
fn()
// --- 存储到切片 ---
fns := [](func(x int) int){
func(x int) int { return x + 1 },
func(x int) int { return x + 2 },
}
println(fns[0](100))
// --- 结构体字段 ---
d := struct {
fn func() string
}{
fn: func() string { return "Hello, World!" },
}
println(d.fn())
// --- channel传递 ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)())
延迟调用
关键字defer
用于注册延迟调用,通常用于释放资源或者错误处理。多个defer
按照FILO
的顺序执行,哪怕函数或者某个延迟调用发生错误,这些调用依旧会被执行。
func test(x int) {
defer println("a")
defer println("b")
defer func() {
_ = 100 / x
}()
defer println("c")
}
func main() {
test(0)
}
// c
// b
// a
// panic: runtime error: integer divide by zero
延迟调用参数在注册时求值或者复制,可用指针或闭包延迟读取
func test() {
x, y := 10, 100
defer func(i int) {
println(i, y) // 10 110
}(x)
x += 10
y += 10
}
滥用defer
会导致性能问题,尤其是在大循环里。
错误处理
没有结构化异常,使用panic
抛出错误,recover
捕获错误。
func test() {
defer func() {
if err := recover(); err != nil {
println(err.(string))
}
}()
panic("panic error")
}
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
只有在延迟调用内直接调用才会终止错误,否则总会返回nil
,任何未捕获的错误都会沿调用堆栈向外传递。
func test() {
defer recover() // 无效
defer fmt.Println(recover()) // 无效
defer func() {
func() {
recover() // 无效
println("defer inner")
}()
}()
panic("test panic")
}
// defer inner
// <nil>
// panic: test panic
使用延迟匿名函数或者直接调用recover
的签名函数都是有效的。
func except() {
if err := recover(); err != nil {
fmt.Println(err.(string)) // test panic
}
}
func test() {
defer except()
panic("test panic")
}
将代码重构成匿名函数,可确保后续代码执行。
func div(x, y int) {
var z int
func() {
defer func() {
if err := recover(); err != nil {
z = 0
}
}()
z = y / x
}()
fmt.Println("y/x=", z)
}
除用panic
引发中断型错误外,还可返回error
类型错误对象来表示函数调用状态。
var ErrDivbyZero = errors.New("division by 0")
func div(x, y int) (int, error) {
if 0 == x {
return 0, ErrDivbyZero
}
return y / x, nil
}
func main() {
switch z, err := div(0, 20); err {
case nil:
println(z)
case ErrDivbyZero:
panic(err) // panic: division by 0
}
}
- 导致流程出现不可修复性的错误时用
panic
; - 其它情况使用
error
;