在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
defer语句被用于预定对一个函数的调用。我们把这类被defer语句调用的函数称为延迟函数。注意,defer语句只能出现在函数或方法的内部。
defer fmt.Println("The finishing touches.") 这里的表达式语句必须代表一个函数或方法的调用 defer语句的执行时机总是在直接包含它的那个函数把流程控制权交还给它的调用方的前一刻,无论defer语句出现在外围函数的函数体中的哪一个位置上。具体分为下面几种情况:
当外围函数的函数体中的相应语句全部被正常执行完毕的时候,只有在该函数中的所有defer语句都被执行完毕之后该函数才会真正地结束执行。
当外围函数的函数体中的return语句被执行的时候,只有在该函数中的所有defer语句都被执行完毕之后该函数才会真正地返回。
当在外围函数中有运行时恐慌发生的时候,只有在该函数中的所有defer语句都被执行完毕之后该运行时恐慌才会真正地被扩散至该函数的调用方。
总之,外围函数的执行的结束会由于其中defer语句的执行而被推迟。
正因为defer语句有着这样的特性,所以它成为了执行释放资源或异常处理等收尾任务的首选。使用defer语句的优势有两个:一、收尾任务总会被执行,我们不会再因粗心大意而造成资源的浪费;二、我们可以把它们放到外围函数的函数体中的任何地方(一般是函数体开始处或紧跟在申请资源的语句的后面),而不是只能放在函数体的最后。这使得代码逻辑变得更加清晰,并且收尾任务是否被合理的指定也变得一目了然。
defer func() { fmt.Println("The finishing touches.") }() 注意,一个针对匿名函数的调用表达式是由一个函数字面量和一个代表了调用操作的一对圆括号组成的。 我们在这里选择匿名函数的好处是可以使该函数的收尾任务的内容更加直观。不过,我们也可以把比较通用的收尾任务单独放在一个命名函数中,然后再将其添加到需要它的defer语句中。无论在defer关键字右边的是命名函数还是匿名函数,我们都可以称之为延迟函数。因为它总是会被延迟到外围函数执行结束前一刻才被真正的调用。
func begin(funcName string) string { fmt.Printf("Enter function %s.\n", funcName) return funcName } func end(funcName string) string { fmt.Printf("Exit function %s.\n", funcName) return funcName } func record() { defer end(begin("record")) fmt.Println("In function record.") } outputs: Enter function record.
In function record.
Exit function record.
这样做除了可以避免参数值在延迟函数被真正调用之前再次发生改变而给该函数的执行造成影响之外,还是处于同一条defer语句可能会被多次执行的考虑。如下例:
func printNumbers() { for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } } outputs: 4 3 2 1 0
fmt.Printf("%d ", 4) fmt.Printf("%d ", 3) fmt.Printf("%d ", 2) fmt.Printf("%d ", 1) fmt.Printf("%d ", 0) 例: func appendNumbers(ints []int) (result []int) { result = append(ints, 1) fmt.Println(result) defer func() { result = append(result, 2) }() result = append(result, 3) fmt.Println(result) defer func() { result = append(result, 4) }() result = append(result, 5) fmt.Println(result) defer func() { result = append(result, 6) }() return result } outputs: [0 1 3 5 6 4 2]
func printNumbers() { for i := 0; i < 5; i++ { defer func() { fmt.Printf("%d ", i) }() } } outputs: 5 5 5 5 5
在defer语句被执行的时候传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在那时被求值。当我们把
fmt.Printf("%d ", i) defer func() { fmt.Printf("%d ", i) }() defer func() { fmt.Printf("%d ", i) }() 在printNumbers函数的执行即将结束的时候,那个专属列表中的延迟函数调用表达式就会被逆序的取出并被逐个的求值。然而,这时的变量i已经被修改为了5。因此,对5个相同的调用表达式的求值都会使标准输出上打印出5.
如何修正这个问题呢?
defer func(i int) { fmt.Printf("%d ", i) }(i) defer func(i int) { fmt.Printf("%d ", i) }(0) 。
如果延迟函数是一个匿名函数,并且在外围函数的声明中存在命名的结果声明,那么在延迟函数中的代码是可以对命名结果的值进行访问和修改的。如下例:
func modify(n int) (number int) { fmt.Println(number) defer func() { number += n }() number++ return } ,结果为:3
虽然在延迟函数的声明中可以包含结果声明,但是其返回的结果值会在它被执行完毕时丢弃。因此,作为惯例,我们在编写延迟函数的声明的时候不会为其添加结果声明。另一方面,推荐以传参的方式提供延迟函数所需的外部值。如下例:
我们可以把想要传递给延迟函数的参数值依照规则放入到那个代表调用操作的圆括号中,就像调用普通函数那样。另一方面,虽然我们在延迟函数的函数体中返回了结果值,但是却不会产生任何效果。
|
请发表评论