为了账号安全,请及时绑定邮箱和手机立即绑定

当 defer 在同一个变量上被调用两次时会发生什么?

当 defer 在同一个变量上被调用两次时会发生什么?

Go
Qyouu 2021-09-20 10:59:07
当该方法的结构已更改时,两次调用 defer 会发生什么?例如:rows := Query(`SELECT FROM whatever`)defer rows.Close()for rows.Next() {   // do something}rows = Query(`SELECT FROM another`) defer rows.Close()for rows.Next() {  // do something else}哪个rows时候上次rows.Close()打电话?
查看完整描述

3 回答

?
qq_花开花谢_0

TA贡献1835条经验 获得超7个赞

这取决于方法接收者和变量的类型。


简短回答:如果您正在使用该database/sql包,您的延迟Rows.Close()方法将正确关闭您的两个Rows实例,因为Rows.Close()有指针接收器并且因为DB.Query()返回一个指针(rows也是一个指针)。请参阅下面的推理和解释。


为避免混淆,我建议使用不同的变量,并且很清楚您想要什么以及将关闭什么:


rows := Query(`SELECT FROM whatever`)

defer rows.Close()

// ...

rows2 := Query(`SELECT FROM whatever`)

defer rows2.Close()

我想指出一个重要的事实,它来自延迟函数及其参数被立即评估,这在Effective Go博客文章和语言规范:延迟语句中也有说明:


每次执行“defer”语句时,函数值和调用的参数都会像往常一样评估并重新保存,但不会调用实际的函数。相反,在周围函数返回之前立即调用延迟函数,以与延迟相反的顺序调用。


如果变量不是指针:当调用延迟的方法时,您将观察到不同的结果,这取决于该方法是否具有指针接收器。

如果变量是一个指针,您将始终看到“期望”的结果。


看这个例子:


type X struct {

    S string

}


func (x X) Close() {

    fmt.Println("Value-Closing", x.S)

}


func (x *X) CloseP() {

    fmt.Println("Pointer-Closing", x.S)

}


func main() {

    x := X{"Value-X First"}

    defer x.Close()

    x = X{"Value-X Second"}

    defer x.Close()


    x2 := X{"Value-X2 First"}

    defer x2.CloseP()

    x2 = X{"Value-X2 Second"}

    defer x2.CloseP()


    xp := &X{"Pointer-X First"}

    defer xp.Close()

    xp = &X{"Pointer-X Second"}

    defer xp.Close()


    xp2 := &X{"Pointer-X2 First"}

    defer xp2.CloseP()

    xp2 = &X{"Pointer-X2 Second"}

    defer xp2.CloseP()

}

输出:


Pointer-Closing Pointer-X2 Second

Pointer-Closing Pointer-X2 First

Value-Closing Pointer-X Second

Value-Closing Pointer-X First

Pointer-Closing Value-X2 Second

Pointer-Closing Value-X2 Second

Value-Closing Value-X Second

Value-Closing Value-X First

在Go Playground上试一试。


使用指针变量的结果总是好的(如预期的那样)。


使用非指针变量和指针接收器,我们会看到相同的打印结果(最新的),但如果我们有值接收器,它会打印 2 个不同的结果。


非指针变量说明:


如前所述,包括接收器在内的延迟函数在defer执行时被评估。如果是指针接收器,它将是局部变量的地址。因此,当您为其分配一个新值并调用 another 时defer,指针接收器将再次使用本地变量的相同地址(只是指向的值不同)。所以稍后在执行函数时,两者都会使用相同的地址两次,但指向的值将相同,即稍后分配的值。


在值接收器的情况下,接收器是执行时创建的副本defer,因此如果您为变量分配一个新值并调用 another defer,则会创建另一个与前一个不同的副本。


查看完整回答
反对 回复 2021-09-20
?
猛跑小猪

TA贡献1858条经验 获得超8个赞

有效的围棋提到:

延迟函数的参数(如果函数是方法,则包括接收者)在延迟执行时计算,而不是在调用执行时计算

除了避免担心函数执行时变量会改变值,这意味着单个延迟调用站点可以延迟多个函数执行

在您的情况下,延迟将引用第二行实例。
这两个延迟函数以 LIFO 顺序执行(也如“延迟、恐慌和恢复”中所述)。

正如icza在他的回答和评论中提到的:

2 个延迟Close()方法将引用2 个不同的 Rows 值,并且两者都将被正确关闭,因为rows指针,而不是值类型。


查看完整回答
反对 回复 2021-09-20
  • 3 回答
  • 0 关注
  • 264 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信