defer
是Go的特有发明,常用来执行文件关闭等资源清理的工作。
package main
import (
"io"
"os"
)
func main() {
f, err := os.Open("go.mod") // 打开一个文件
if err != nil { // 如果打开不成功 err != nil
os.Exit(1) // 直接终止程序,1表示非正常退出
}
defer f.Close() // 在main函数退出前执行。
io.Copy(os.Stdout, f) // 输出到命令行
}
defer
将 f.Close()
函数延迟到main
函数退出前才执行。
defer
会把要延迟运行的函数放到一个栈里(先进后出), 在defer
所在的函数运行结束后再从栈里一个个拿出来运行。
package main
import "fmt"
func main() {
defer func() {
fmt.Println(1)
}()
defer func() {
fmt.Println(2)
}()
defer func() {
fmt.Println(3)
}()
}
输出
多个defer
语句是放在栈
中,在main
函数退出前从栈中(先进后出)一个个取出运行。
defer
延迟的函数什么时候运行? 在**defer
所在的函数** 退出前运行。
package main
import "fmt"
func foo() {
defer func() {
fmt.Println(1)
}()
defer func() {
fmt.Println(2)
}()
defer func() {
fmt.Println(3)
}()
}
func main() {
foo()
fmt.Println("main")
}
输出
你能说说为什么会是这个结果吗?
defer 所在函数panic
后,仍然可以执行defer
延迟的函数。
package main
import "fmt"
func foo() {
defer func() {
fmt.Println(1)
}()
defer func() {
fmt.Println(2)
}()
defer func() {
fmt.Println(3)
}()
panic("噢噢 坏事发生了!") // 手动引发panic,终止程序
}
func main() {
foo()
fmt.Println("main")
}
输出
能输出3,2,1但打印不了main
了,因为panic
从foo
函数传到上层的main
函数,main
函数就立刻退出了。
panic是什么?
panic(恐慌)是一种意想不到的错误,或者是一种致命性的错误,程序必须终止。
- 例如除数为0的时候会panic:
package main
import "fmt"
func main() {
a := 0
b := 3 / a // 除数为0, panic
fmt.Println(b)
}
输出panic
panic: runtime error: integer divide by zero
- 索引越界panic
package main
import "fmt"
func main() {
ls := []int{1, 2}
fmt.Println(ls[3]) // 索引越界
}
输出panic:
panic: runtime error: index out of range [3] with length 2
- 自定义panic
package main
func main() {
panic("坏事发送了...") // 手动panic
}
输出panic:
panic()
生成一个自定义panic
, 可以传人任何类型的变量(说明引发panic的原因)。
把panic当做恐慌吧,不要把panic
作为错误处理,Go有自己的错误处理——error
接口。只有意想不到,或者致命性的错误才使用panic
。
在 defer 中使用recover
捕获panic
,使得程序继续运行(恢复)
package main
import "fmt"
func foo() {
defer func() {
if r := recover(); r != nil { // r 是any类型,实际上是字符串: "噢噢 坏事发生了!", 传入panic()的值 //
fmt.Println(r)
}
}()
defer func() {
fmt.Println(1)
}()
defer func() {
fmt.Println(2)
}()
defer func() {
fmt.Println(3)
}()
panic("噢噢 坏事发生了!") // 手动panic,panic接收any类型,表明panic发送的原因
}
func main() {
foo()
fmt.Println("main")
}
输出
使用recover
后main
成功打印。
foo
函数的panic
被recover
捕获到了,没有继续传导到main函数,函数继续运行,打印main
注意, recover
必须在defer
语句中使用,因为defer
在程序panic时仍会运行。
recover()
返回的是any
类型,即引发panic
的原因, 也就是panic()
传进去的类型,在本例中是string: "噢噢 坏事发生了!"
。
- 在循环中使用panic:
package main
import "fmt"
// 模拟文件打开和关闭
func open(file string) (close func()) { // 返回一个close函数
return func() {
fmt.Printf("%s 关闭成功\n", file)
}
}
func main() {
for i := range 5 {
close := open(fmt.Sprintf("%d文件", i))
defer close()
}
}
输出
4文件 关闭成功
3文件 关闭成功
2文件 关闭成功
1文件 关闭成功
0文件 关闭成功
我们希望文件用完就关闭,而不是等待main函数退出是再关闭。使用一个匿名函数套住defer
package main
import "fmt"
// 模拟文件打开和关闭
func open(file string) (close func()) {
return func() {
fmt.Printf("%s 关闭成功\n", file)
}
}
func main() {
for i := range 5 {
func() {
close := open(fmt.Sprintf("%d文件", i))
defer close()
}()
}
}
输出
0文件 关闭成功
1文件 关闭成功
2文件 关闭成功
3文件 关闭成功
4文件 关闭成功
每次循环结束匿名函数退出前触发defer
语句。记住,defer
所在的函数退出前运行延迟函数。
- defer函数的参数在声明时就验证
package main
import "fmt"
func main() {
a := 1
defer func(num int) { // 参数num
fmt.Println(num)
}(a) // 传入a 立即验证,此时 a = 1
a = 2
}
函数该打印1, 还是2呢?
运行,输出
原因是defer
语句声明时就进行验证,此时a
=1
如果是下面代码,会发送什么?
package main
import "fmt"
func main() {
a := 1
defer func() { // 没有参数
fmt.Println(a) // 闭包,引用外部变量a
}()
a = 2
}
输出
原因是只有参数进行验证。上述的defer后面函数是一个闭包,引用了外部变量a
, 在函数退出时, a = 2
所以打印2。
- defer 与命名返回值
考虑下列代码,会输出什么?
package main
import "fmt"
func bar() (num int) { // 命名返回值num
defer func() {
num = 3 // 闭包,把3赋值给外部变量num
}()
num = 1
return 2 // 等价有 num = 2
}
func main() {
fmt.Println(bar())
}
是输出1,2还是3呢?
运行,输出
为什么呢? return 2
相当于num = 2
, 之后defer
函数运行,num = 3
, 所以返回3。defer 可以改变函数 return
后的命名返回值。