[Golang早读] defer用法总结

212 阅读3分钟

defer是Go语言中的一个关键字(延迟调用),一般用于释放资源、数据库连接、关闭文件句柄、释放锁等

1. defer的执行顺序

当函数中含有多个defer语句时,按照先进后出的方式执行

func main() {
   var whatever [5]struct{}
   for i := range whatever {
      defer fmt.Println(i)
   }
}

/*
4
3
2
1
0
*/

所有的defer语句都会被放入栈中,在入栈的时候会进行相关的值拷贝,也就是所谓的对应参数实时解析

2. 声明defer时,对应的参数会实时解析

func main() {
   i := 1
   fmt.Println("1 i =", i)
   defer fmt.Print(i)
   i++
   fmt.Println("2 i =", i)
}

/* output
1 i = 1
2 i = 2
1 
*/

简单一句话就是,defer语句中的变量,在defer声明时就决定了

defer后面跟无参函数,有参函数,方法时:

func test(a int) { //无返回值函数
   defer fmt.Println("1、a =", a)                    //方法
   defer func(v int) { fmt.Println("2、a =", v) }(a) //有参函数
   defer func() { fmt.Println("3、a =", a) }()       //无参函数
   a++
}
func main() {
   test(1)
}

/* output
3、a = 2
2、a = 1
1、a = 1
*/

方法中的参数a,有参函数中的v,会请求参数,将参数解析带入,所以输出都是1,执行到a++后,a变成2,3个defer语句以后声明先执行的顺序执行,无参函数中使用的a现在已经是2

3. 可读取函数返回值(return返回机制)

defer、return、返回值三者的执行逻辑是:

  1. return最先执行,return负责将结果写入返回值中
  2. 接着defer进行收尾工作
  3. 最后函数携带当前返回值(可能和最初返回值不相同)退出

ps:当defer放在return后面时,不会被执行

无名返回值

func a() int {
	var i int
	defer func() {
		i++
		fmt.Println("defer2:", i) 
	}()
	defer func() {
		i++
		fmt.Println("defer1:", i) 
	}()
	return i
}

func main() {
	fmt.Println("return:", a()) 
}

/*
defer1: 1
defer2: 2
return: 0
*/

返回值由变量i赋值,相当于返回值=i=0。第二个defer中i++后i=2,所以最终i的值是2,但是返回值已经在被记录过了,即使后续操作i也不会影响返回值,最终返回值返回0

有名返回值

func a() (i int) {
   defer func() {
      i++
      fmt.Println("defer2:", i)
   }()
   defer func() {
      i++
      fmt.Println("defer1:", i)
   }()
   return i
}

func main() {
   fmt.Println("return:", a())
}

/*
defer1: 1
defer2: 2
return: 2
*/

函数已经指明返回值就是i,所以后续对i的操作都相当于在修改返回值,所以最终函数的返回值是2

函数返回值为地址

func c() *int {
   var i int
   defer func() {
      i++
      fmt.Println("defer2:", i)
   }()
   defer func() {
      i++
      fmt.Println("defer1:", i)
   }()
   return &i
}

func main() {
   fmt.Println("return:", *(c()))
}

/*
defer1: 1
defer2: 2
return: 2
*/

此时的返回值是一个指针地址,这个指针等于&i,相当于指向变量i所在的地址,两个defer语句都对i进行了修改,那么返回值指向的地址内容也发生了变化,所以最终的返回值是2

综合来看一个例子:

func f() (r int) {
    defer func() {
        r += 5
    }()
    return 1
}
func main() {
    fmt.Println(f())
}

// 6

解释:由于return最先执行且返回值是有名的,此时r=1,接着执行defer后的函数此时r=5+1=6,最后返回函数返回值r=6

4. defer和panic

go语言中没有try catch这样对于运行时异常进行捕获和处理的语句,但是可以通过panic/recover模式来处理错误,panic可以在任何地方引发,但是recover只有在defer调用的函数中才有效。另外,defer一定要在可能引发panic的语句之前定义

func f() {
   defer func() {
      if err := recover(); err != nil {
         fmt.Println("Panic...")
      }
   }()
   panic("this is a panic")
}