lang

LangBook

8.defer

defer是Go的特有发明,常用来执行文件关闭等资源清理的工作。

1. 资源清理:

main.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) // 输出到命令行
}

deferf.Close()函数延迟到main函数退出前才执行。

2. defer 的原理

defer 会把要延迟运行的函数放到一个栈里(先进后出), 在defer所在的函数运行结束后再从栈里一个个拿出来运行。

main.go
package main
 
import "fmt"
 
func main() {
	defer func() {
		fmt.Println(1)
	}()
 
	defer func() {
		fmt.Println(2)
	}()
 
	defer func() {
		fmt.Println(3)
	}()
}

输出

3
2
1

多个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") 
}

输出

3
2
1
main

你能说说为什么会是这个结果吗?

3. recover

defer 所在函数panic后,仍然可以执行defer 延迟的函数。

main.go
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
panic: 噢噢 坏事发生了!

能输出3,2,1但打印不了main了,因为panicfoo函数传到上层的main函数,main函数就立刻退出了。

panic是什么?

panic(恐慌)是一种意想不到的错误,或者是一种致命性的错误,程序必须终止。

  1. 例如除数为0的时候会panic:
main.go
package main
 
import "fmt"
 
func main() {
	a := 0
	b := 3 / a // 除数为0, panic
	fmt.Println(b)
}

输出panic

panic: runtime error: integer divide by zero
  1. 索引越界panic
main.go
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
  1. 自定义panic
main.go
package main
 
func main() {
	panic("坏事发送了...") // 手动panic
}

输出panic:

panic: 坏事发送了...

panic()生成一个自定义panic, 可以传人任何类型的变量(说明引发panic的原因)。

把panic当做恐慌吧,不要把panic作为错误处理,Go有自己的错误处理——error接口。只有意想不到,或者致命性的错误才使用panic

在 defer 中使用recover捕获panic,使得程序继续运行(恢复)

main.go
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")
}

输出

3
2
1
噢噢 坏事发生了!
main

使用recovermain成功打印。

foo函数的panicrecover捕获到了,没有继续传导到main函数,函数继续运行,打印main

注意, recover必须在defer 语句中使用,因为defer在程序panic时仍会运行。

recover()返回的是any类型,即引发panic的原因, 也就是panic()传进去的类型,在本例中是string: "噢噢 坏事发生了!"

4. 常见错误

  1. 在循环中使用panic:
main.go
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

main.go
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所在的函数退出前运行延迟函数。

  1. defer函数的参数在声明时就验证
main.go
package main
 
import "fmt"
 
func main() {
	a := 1
	defer func(num int) { // 参数num
		fmt.Println(num) 
	}(a) // 传入a 立即验证,此时 a = 1
 
	a = 2 
}

函数该打印1, 还是2呢?

运行,输出

1

原因是defer语句声明时就进行验证,此时a=1

如果是下面代码,会发送什么?

main.go
package main
 
import "fmt"
 
func main() {
	a := 1
	defer func() { // 没有参数 
		fmt.Println(a) // 闭包,引用外部变量a
	}() 
 
	a = 2
}

输出

2

原因是只有参数进行验证。上述的defer后面函数是一个闭包,引用了外部变量a, 在函数退出时, a = 2 所以打印2。

  1. defer 与命名返回值

考虑下列代码,会输出什么?

main.go
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呢?

运行,输出

3

为什么呢? return 2相当于num = 2, 之后defer函数运行,num = 3, 所以返回3。defer 可以改变函数 return 后的命名返回值。

On this page