golang之defer理解

版权声明:转载请注明出处,谢谢。 https://blog.csdn.net/butterfly5211314/article/details/83512711

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也是使用的静态作用域。

扫描二维码关注公众号,回复: 3866328 查看本文章

针对非指针类型调用函数

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会执行一次拷贝。

总结

  • 值接收者调用方法会执行拷贝
  • 静态作用域

参考

欢迎补充指正!

猜你喜欢

转载自blog.csdn.net/butterfly5211314/article/details/83512711