cloudaice / learnGo

there are something about Go Useage
MIT License
0 stars 0 forks source link

关于defer的使用问题 #13

Open cloudaice opened 9 years ago

cloudaice commented 9 years ago

关于defer 传参问题

下面两段代码的输出是不一样的:

package main

import (
    "fmt"
)

func TestDefer(i int) {
    fmt.Println(i)
}

func main() {
    i := 0
    defer TestDefer(i)
    i = 100
}
package main

import (
    "fmt"
)

func TestDefer(i int) {
    fmt.Println(i)
}

func main() {
    i := 0
    defer func() {
        TestDefer(i)
    }()
    i = 100
}

前一段代码输出的结果是0,后一段代码输出的是100。原因在于使用defer的时候,defer表达式的参数在defer表达式定义的时候就已经明确。在return之前再执行具体的函数内容,因此第一段代码在defer表达式定义的时候,传入defer表达式的参数就已经被传入,Golang中传参默认都是拷贝的,因此,即使后面修改了值,也不会影响defer表达式中的打印输出。

cloudaice commented 9 years ago

使用命名返回值的时候,使用defer表达式可以修改命名返回值的内容,如下面代码所示:

package main

import (
    "fmt"
)

func TestDefer() (i int) {
    defer func() {
        fmt.Println(i)
        i++
        fmt.Println(i)
    }()
    return 1
}

func main() {
    fmt.Println(TestDefer())
}

这段代码的输出结果是

1
2
2

TestDefer函数返回语句是return 1,但是,最终打印的结果是2。在对i进行++之前,打印的i的值为1,这说明在return 1这条语句里,先把·return后面的值计算出来,然后赋值给i,接着调用defer语句,对i做了相应的修改。这里可以看出deferreturn两个语句的执行顺序,并不是在执行到return语句之前,就去调用defer语句,而是,计算出return语句后面值之后,然后调用return将值返回。

cloudaice commented 9 years ago

deferreturn的关系非常密切。这里举出另外一个代码例子:

package main

import (
    "fmt"
)

func TestDefer() int {
    i := 0
    defer func() {
        fmt.Println(i)
        i++
        fmt.Println(i)
    }()
    return i
}

func main() {
    fmt.Println(TestDefer())
}

这段代码的输出是

0
1
0

这里还要从执行return语句的语义开始说起,当函数运行到return语句的时候,如上面所说,会把return后面的表达式计算出来,放到一个待返回的变量槽里面,然后再去执行defer表达式。因此defer表达式里便会打印出0, 1。最后把返回槽里面的值返回给调用者。在这个过程里,在碰到return语句的时候,i 已经被赋值给返回槽的值了。而最终返回的时候只会返回槽里的值,因此在defer语句中再修改i的值也没有什么用了。但是命名返回值就会不一样了,命名返回值可以认为就是对应返回槽,只是在没有命名返回值的时候,那个返回槽被隐藏起来。所以可以理解为所有的带返回值的函数,都是有一个返回槽用于存放要返回的值的。使用命名返回值的时候,可以显式地对槽内的变量进行修改。这也是为什么带命名返回值的函数,在defer表达式里面可以修改返回值了。