defer
调用是一个栈结构
defer会在函数退出前执行,而且满足后进先出
,类似栈.
直接调用deferCommon会输出:done,9,8,…,0
func deferCommon() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
defer
的作用域是一个函数,不是一个语句块
事实上上述代码会有一个警告,不过使用打印可能不太明显,
最常见的读文件为例子来看看:
func deferLoop() {
filenames := []string{"1.txt", "2.txt"}
for _, filename := range filenames {
fp, err := os.Open(filename)
if err != nil {
log.Printf("open file %s failed, err:%v", filename, err)
continue
}
// Possible resource leak, 'defer' is called in a for loop.
// 这句代码会造成循环结束后才开始回收资源,而不是执行了一次循环就回收一次资源
defer fp.Close()
// 使用fp(file pointer)
}
}
上面这种defer调用可能会造成资源无法回收,可以去掉defer直接调fp.Close().
链式调用
简单来说,在defer x.m1().m2()
中,m1会直接被调用,而m2会在最后被调用,
类似:
x.m1()
defer x.m2()
看测试代码:
type logger struct{}
func (l *logger) Print(s interface{}) {
fmt.Printf("Log: %v\n", s)
}
type customLogger struct {
l *logger
}
func (f *customLogger) Logger() *logger {
fmt.Println("Logger()")
return f.l
}
func do(f *customLogger) {
// f.Logger()会“直接调用”,而f.Print()会“延迟调用”
defer f.Logger().Print("done")
fmt.Println("do")
}
// 如果你在一个 defer 语句中链式调用方法,
// 那么除了最后一个函数以外其余函数都会在调用时直接执行。
// defer 要求一个函数作为 “参数” 。
// 输出:
// Logger()
// do
// Log: done
func deferFuncChain() {
f := &customLogger{
l: &logger{},
}
do(f)
}
静态使用域
在看明白了上面链式调用的例子后,再进一步,
如果修改上述的do
方法如下, 又会如何(logger及customLogger的定义和方法都不变):
func do2(f *customLogger) (err error) {
defer f.Logger().Print(err)
fmt.Println("do")
// 和直接return fmt.Errorf("ERROR")一样
// err = fmt.Errorf("ERROR")
// return
return fmt.Errorf("ERROR")
}
// 输出:
// Logger()
// do
// Log: <nil>
// 最后的Log: <nil>说明,golang是使用的是静态作用域
func deferFuncChainWithParam() {
f := &customLogger{
l: &logger{},
}
do2(f)
}
重点说下最后那个Log: <nil>
, 个人理解是 因为golang是使用的是静态作用域。
由于在 defer f.Logger().Print(err) 这句前,
err已经声明了 *func do2(f customLogger) (err error) { , 此时err是nil.
可以给一个静态作用域的例子:
var a = 1
func test() {
fmt.Println("in test, a is", a)
}
func main() {
var a = 0
fmt.Println("in main, a is", a)
test()
fmt.Println("after test() call, a is", a)
}
上述代码输出为:
in main, a is 0
in test, a is 1
after test() call, a is 0
在我理解,test()引用的是最开始那个a,也就体现了golang使用的是静态作用域。
顺便提一句,js也是使用的静态作用域。
针对非指针类型调用函数
type project struct {
tagline string
}
func (p project) log() {
fmt.Printf("project: %v\n", p)
}
func deferCopy() {
m := project{"test"}
defer m.log()
m.tagline = "test2"
}
输出结果:
test
如果把func (p project) log() {
改成func (p *project) log() {
, 那么会输出test2。
因为非指针类型调用log会执行一次拷贝。
总结
- 值接收者调用方法会执行拷贝
- 静态作用域
参考
- https://studygolang.com/articles/12791
- https://medium.com/@i0exception/some-common-traps-while-using-defer-205ebbdc0a3b
欢迎补充指正!