defer基本用法和注意事项
1.defer基本用法:
package main
import "fmt"
func sum(n1 int, n2 int) int {
//当执行到defer时,暂时不执行,会将其压入一个独立的栈中
//和sum栈和main栈不在一个地方的栈
//当函数执行完毕后,再从defer栈中按照先入后出原则出栈
defer fmt.Println("ok1,n1=", n1) //5.再执行这句
defer fmt.Println("ok2,n2=", n2) //4.再执行这句
res := n1 + n2 //1.先执行了这句
fmt.Println("ok3=", res, "n1=", n1, "n2", n2) //2.再执行这句
return res //3.再执行这句
}
func main() {
res := sum(20, 30)
fmt.Println("res=", res) //6.再执行这句
}
1.defer用于注册一个延迟调用
2.多个defer的执行顺序为“后进先出”;
3.所有函数在执行RET返回指令之前,都会先检查是否存在defer语句,若存在则先逆序调用defer语句进行收尾工作再退出返回;
4.匿名返回值是在return执行时被声明,有名返回值则是在函数声明的同时被声明,因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值;
5.return其实应该包含前后两个步骤:第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);第二步是调用RET返回指令并传入返回值, 而RET则会检查defer是否存在,若存在就先逆序插播defer语句,最后RET携带返回值退出函数;
6.因此,defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。
2.注意事项
再看下面一段代码:
package main
import "fmt"
func sum(n1 int, n2 int) int {
defer fmt.Println("ok1,n1=", n1)
defer fmt.Println("ok2,n2=", n2)
n1++
n2++
res := n1 + n2
fmt.Println("ok3=", res, "n1=", n1, "n2", n2)
return res
}
func main() {
res := sum(20, 30)
fmt.Println("res=", res)
}
输出结果是:
ok3= 52 n1= 21 n2 31
ok2,n2= 30
ok1,n1= 20
res= 52
这段代码证明了:当defer把语句放入到入栈时,如果涉及到值时,会将相应的值拷贝同时入栈
再看下面这段代码:
package main
import "fmt"
func sum(n1 *int, n2 *int) int {
defer fmt.Println("ok1,n1=", *n1)
defer fmt.Println("ok2,n2=", *n2)
*n1++
*n2++
res := *n1 + *n2
fmt.Println("ok3=", res, "n1=", *n1, "n2", *n2)
return res
}
func main() {
n1 := 20
n2 := 30
var p1 *int = &n1
var p2 *int = &n2
res := sum(p1, p2)
fmt.Println("res=", res)
}
输出结果是:
ok3= 52 n1= 21 n2 31
ok2,n2= 30
ok1,n1= 20
res= 52
实践证明引用传递入栈时也是拷贝入栈
3.defer最主要的作用
当函数执行完毕后,及时释放函数所创建的资源
例如下面的例子:
1.
func test(){
//关闭文件资源
file=openfile(文件名)
defer file.close()
//写在打开文件语句后是因为:
//怕自己最后忘了关闭文件,因此在打开的时候便写defer 关闭
//同时后续还可以使用这个文件
//传统php,java资源关闭不易控制时机
//使得我们不再为什么时候关闭资源感到烦恼
}
func test(){
//释放数据库资源
connect=openDatebse()
defer connect.close()
//其余代码
}