再过5分钟你就能了解Go语言匿名返回值和命名返回值对defer的影响啦

232 阅读2分钟

1. 匿名返回值

匿名返回值未对函数的返回值作命名

func anonymousReturnValues() int {
	var res int
	defer func() {
		res++
		fmt.Println("defer")
	}()
	return res
}

返回值是int类型未指定返回值名称,必须在函数内部return语句进行指定返回值名称,不指定则报错!

2. 命名返回值

命名返回值给一个函数返回值指定名字。 如果指定了返回值名字,则在该函数的第一行中隐含定义了该名字的变量。

func namedReturnValues() (res int) {
	//var s int = 3
	defer func() {
		res++
		fmt.Println("defer")
	}()
        //return s
	return
}

函数返回值指定返回变量名称,并且不需要在return语句后特别指定返回的变量,go在遇到return时会自动将res返回!

如果特别指定了返回变量呢?如上代码屏蔽部分,开始定义整形变量s那么最后return s返回的是什么呢?经过测试返回结果如下:

返回: 4,若是没有defer语句则返回:3,可以看出,虽然指定了变量但是最终返回执行的时候应该有如下这样的赋值代码:

res = s

说明命名返回值返回的永远是函数指定的命名返回值res。

3. defer在命名与匿名返回值函数中的表现

这里首先普及下defer的执行机制

  1. 包裹defer的函数返回时
  2. 包裹defer的函数执行到末尾时
  3. 所在的goroutine发生panic时
func anonymousReturnValues() int {
	var res int
	defer func() {
		res++
		fmt.Println("defer")
	}()
	return res
}
 
func namedReturnValues() (res int) {
	//var s int = 3
	defer func() {
		res++
		fmt.Println("defer")
	}()
	//fmt.Printf("%d\n", s)
	return
}

返回值分别为0和1,匿名返回0,命名返回1,这是什么原因呢?

这里以匿名返回函数作说明,执行过程如下:

  1. 首先函数返回时会自动创建一个返回变量假设为ret,函数返回时要将res赋值给ret,即有ret = res,也就是说ret=0
  2. 然后检查函数中是否有defer存在,若有则执行defer中部分,此时就到了res++
  3. 最后返回ret

从上面过程可以看到,函数返回的是ret,即0,虽然defer中res++但是是给res做加减,res和ret是两个变量;但是在命名返回值中就会有不一样的结果,因为返回值在函数定义时以经存在,return时不需要再创建另外的变量ret,返回的ret就是res,只有一个变量,所以res++就是给实际返回的ret做加减,最终返回结果当然是1了。

defer在命名和匿名返回函数中表现不一样,这是一个很大的坑,使用中要多多注意。