Go语言学习 十一 defer语句
Go的defer
语句用于延迟调用函数,该语句在执行defer
的函数返回之前立即执行,换句话说,defer
将函数推迟到外层函数执行完毕但返回之前执行。这在处理那些必须释放资源等的情况下非常有用,无论当前函数执行结果如何。
类似于Java中的finally
或try-with-resources
。
一 defer语句
语法:defer function | method
defer
只能后跟函数或方法。对内置函数的调用受限于表达式的限制。
表达式的限制:
表达式上下文中不允许使用以下内置函数:
append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
示例:
package main
import (
"fmt"
)
func main() {
defer fmt.Println("world") // 该行将在hello之后打印
fmt.Println("hello")
}
另外,如果延迟函数值的计算结果为nil
,则在执行延迟函数时会发生恐慌,而不是在执行defer
语句时。
如果被推迟的函数是一个函数字面量,且包裹着它的函数有命名的结果参数在其访问范围内,延迟函数可能会在那些结果参数返回之前访问并修改它们。如果被推迟的函数有返回值,它们将在返回时被丢弃。
示例:
lock(l)
defer unlock(l) // unlock将在外层函数返回前执行
// 外层函数返回前会打印出3 2 1 0
for i := 0; i <= 3; i++ {
defer fmt.Print(i)
}
// f 返回 42
func f() (result int) {
defer func() {
// result在被下边的return设置为6之后又在此被访问并修改
result *= 7
}()
return 6
}
释放互斥锁或关闭文件的例子:
// Contents 以字符串形式返回文件内容。
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close 将在完成时运行。
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append稍后讨论
if err != nil {
if err == io.EOF {
break
}
return "", err // 如果在此返回,f 将被关闭
}
}
return string(result), nil // 如果在此返回,f 将被关闭
}
推迟对类似Close
之类函数的调用有两点好处。首先,能保证不会忘记关闭文件;其次,这意味着“关闭”位于“打开”附近,这比将其放在函数结尾要清晰得多。
推迟函数的参数(如果函数是一个方法,则包括接收器)在defer
执行时就会计算,而不是在调用执行时计算。除了避免担心变量在函数执行时被改变值之外,还意味着单个延迟调用可以推迟多个函数的执行,例如:
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
1.1 斐波那契闭包的defer版本
借助defer的特性,我们还可以这样实现上一篇文章中的斐波那契闭包:
package main
import "fmt"
func fibonacci() func() int {
a, b := 0, 1
return func() int {
defer func() {
a, b = b, a+b
}()
return a
}
}
func main() {
f := fibonacci()
for i := 0; i < 15; i++ {
fmt.Println(f())
}
}
二 defer栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数以后进先出(LIFO
)的顺序执行,所以上个小节中的代码执行完打印的结果为4 3 2 1 0
。
一个更合理的例子是通过程序跟踪函数执行的简单方法:
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
// Use them like this:
func a() {
trace("a")
defer untrace("a")
// do something....
}
我们可以通过利用defer
执行时计算延迟函数的参数这个特点来做一些更有意义的事情。跟踪例程可以设置反跟踪例程的参数。如下:
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
上述代码会输出:
entering: b
in b
entering: a
in a
leaving: a
leaving: b
更多关于 defer 语句的信息,请阅读此博文。
参考:
https://golang.org/doc/effective_go.html#defer
https://golang.org/ref/spec#Defer_statements
版权声明:知识共享署名-非商用-非衍生 (CC BY-NC-ND 3.0) 转载请注明出处