Go语言学习 十一 defer语句

本文最初发表在我的个人博客,查看原文,获得更好的阅读体验


Go的defer语句用于延迟调用函数,该语句在执行defer的函数返回之前立即执行,换句话说,defer将函数推迟到外层函数执行完毕但返回之前执行。这在处理那些必须释放资源等的情况下非常有用,无论当前函数执行结果如何。

类似于Java中的finallytry-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 斐波那契闭包的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值