大家一起学Golang——函数、闭包、defer、recover
函数执行流程
go代码编译完后,函数会以计算机指令的方式存放在代码区,函数定义的形参和局部变量没有调用的时候 是不占用内存,只有调用时会加载到内存中。
在介绍计算机指令形式的代码在内存运行的状况前,先要说下栈(stack),几乎每个程序都使用栈,这个栈满足先进后出的顺序,保存函数调用所需要的信息,被称为堆栈帧(stack frame)
一个函数调用所需要的信息:
- 函数返回的地址
- 函数的参数
- 保存的上下文
下例子来分析函数在栈中情况:
func demoAppend(slice []int){
slice = append(slice, 4,5,6)
}
func main(){
slice := [] int{1,2,3}
demoAppend(slice)
fmt.Println(slice)
}
闭包
函数分为:具名函数和匿名函数
具名函数对应于包级的函数,是匿名函数的一种特例。
当匿名函数引用外部作用域的变量就变成闭包函数,闭包函数是函数式编程语言的核心。
闭包是将函数内部和函数外部连接起来的桥梁
作用:
- 可以读取函数的内部变量
- 变量始终保存在内存当中(与全局变量作用相当)
//闭包函数的一个例子,defer 执行了匿名函数,捕获外部变量v 以引用的方式,这个匿名函数就是闭包
func Demo() (v int){
defer func(){
v++
}()
return 1
}
使用闭包也要注意,调用的局部变量会保存在内存中,会导致内存滥用,使性能降低,要在退出函数前,删除掉不适用的局部变量。
defer
⼀个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
作用:
- 释放占用的资源,如关闭文件,关闭数据库连接,网络连接
- 捕捉处理异常
- 输出日志
下面是个有趣的例子,需要借助对内存栈中的理解,分析defer的调用
func main(){
for i:=0;i<3;i++ {
i:=i //每次循环产生申请一个i变量空间
defer func(){
fmt.Println(i)
}()
}
}
//输出:2 1 0
func main(){
for i:=0;i<3;i++ {
defer func(){
fmt.Println(i)
}()
}
}
//输出:3 3 3
之前,讨论函数 内存栈中的存储逻辑,分析defer 调用 i局部变量的情况。
可见go语言中,分析内存 对理解代码非常帮助。
recover错误拦截
专用于拦截运行时panic的内建函数,常见和defer写在一起来捕获异常。
func Demo(i int) {
var arr [10]int
//设置错误拦截 defer + 匿名函数 +recover
defer func() {
err := recover()
//没有异常不会打印错误
if err != nil {
fmt.Println(err)
}
}()
//数组下标越界
arr[i] = 119
}
func main() {
Demo(10)
}
捕捉错误:runtime error: index out of range [10] with length 10