15.4 有关于defer

说到错误处理,就不得不提defer。

它的规则:

  • 规则一 当defer被声明时,其参数就会被实时解析
  • 规则二 defer执行顺序为先进后出
  • 规则三 defer可以读取有名返回值,也就是可以改变有名返回参数的值。

这三个规则用起来需要注意下,避免出现代码陷阱,下面是具体代码:

// 规则一,当defer被声明时,其参数就会被实时解析
package main
import "fmt"
func main() {
	var i int = 1
	defer fmt.Println("result =>", func() int { return i * 2 }())
	i++
	// 输出: result => 2 (而不是 4)
}
// 规则二 defer执行顺序为先进后出
package main
import "fmt"
func main() {
	defer fmt.Print(" !!! ")
	defer fmt.Print(" world ")
	fmt.Print(" hello ")
}
//输出:  hello  world  !!!

上面讲了两条规则,第三条规则其实也不难理解,只要记住是可以改变有名返回值:

这是由于在Go语言中,return 语句不是原子操作,最先是所有结果值在进入函数时都会初始化为其类型的零值(姑且称为ret赋值),然后执行defer命令,最后才是return操作。如果是有名返回值,返回值变量其实可视为是引用赋值,可以能被defer修改。而在匿名返回值时,给ret的值相当于拷贝赋值,defer命令时不能直接修改。

func fun1() (i int)

上面函数签名中的 i 就是有名返回值,如果fun1()中定义了 defer 代码块,是可以改变返回值 i 的,函数返回语句return i 可以简写为 return 。

这里综合了一下,在下面这个例子里列举了几种情况,可以好好琢磨下:

package main
import (
	"fmt"
)
func main() {
	fmt.Println("=========================")
	fmt.Println("return:", fun1())
	fmt.Println("=========================")
	fmt.Println("return:", fun2())
	fmt.Println("=========================")
	fmt.Println("return:", fun3())
	fmt.Println("=========================")
	fmt.Println("return:", fun4())
}
func fun1() (i int) {
	defer func() {
		i++
		fmt.Println("defer2:", i) // 打印结果为 defer2: 2
	}()
	// 规则二 defer执行顺序为先进后出
	defer func() {
		i++
		fmt.Println("defer1:", i) // 打印结果为 defer1: 1
	}()
	// 规则三 defer可以读取有名返回值(函数指定了返回参数名)
	return 0 //这里实际结果为2。如果是return 100呢
}
func fun2() int {
	var i int
	defer func() {
		i++
		fmt.Println("defer2:", i) // 打印结果为 defer2: 2
	}()
	defer func() {
		i++
		fmt.Println("defer1:", i) // 打印结果为 defer1: 1
	}()
	return i
}
func fun3() (r int) {
	t := 5
	defer func() {
		t = t + 5
		fmt.Println(t)
	}()
	return t
}
func fun4() int {
	i := 8
	// 规则一 当defer被声明时,其参数就会被实时解析
	defer func(i int) {
		i = 99
		fmt.Println(i)
	}(i)
	i = 19
	return i
}

在上面fun1() (i int)有名返回值情况下,return最终返回的实际值和期望的return 0有较大出入。因为在上面fun1() (i int) 中,如果return 100或return 0 ,这样的区别在于i的值实际上分别是100或0。而在上面中,如果return 100,则因为改变了有名返回值i,而defer可以读取有名返回值,所以返回值最终为102,而defer1打印101,defer打印102。因此我们一般直接写为return。

这点要注意,有时函数可能返回非我们希望的值,所以改为匿名返回也是一种办法。具体请看下面输出。

程序输出:
=========================
defer1: 1
defer2: 2
return: 2
=========================
defer1: 1
defer2: 2
return: 0
=========================
10
return: 5
=========================
99
return: 19

使用defer计算函数执行时间

package main
import(
        "fmt"
        "time"
)
func main(){
        defer timeCost(time.Now())
        fmt.Println("start program")
        time.Sleep(5*time.Second)
        fmt.Println("finish program")
}
func timeCost(start time.Time){
        terminal:=time.Since(start)
        fmt.Println(terminal)
}

另外一种计算函数执行时间方法:在对比和基准测试中,我们需要知道一个计算执行消耗的时间。最简单的一个办法就是在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 time 包中的 Now() 和 Sub 函数:

start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
下一节:Go语言函数基本组成:关键字func、函数名、参数列表、返回值、函数体和返回语句。