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

当该方法的结构已更改时,两次调用 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()打电话?



Qyouu
浏览 265回答 3
3回答

qq_花开花谢_0

这取决于方法接收者和变量的类型。简短回答:如果您正在使用该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 SecondPointer-Closing Pointer-X2 FirstValue-Closing Pointer-X SecondValue-Closing Pointer-X FirstPointer-Closing Value-X2 SecondPointer-Closing Value-X2 SecondValue-Closing Value-X SecondValue-Closing Value-X First在Go Playground上试一试。使用指针变量的结果总是好的(如预期的那样)。使用非指针变量和指针接收器,我们会看到相同的打印结果(最新的),但如果我们有值接收器,它会打印 2 个不同的结果。非指针变量说明:如前所述,包括接收器在内的延迟函数在defer执行时被评估。如果是指针接收器,它将是局部变量的地址。因此,当您为其分配一个新值并调用 another 时defer,指针接收器将再次使用本地变量的相同地址(只是指向的值不同)。所以稍后在执行函数时,两者都会使用相同的地址两次,但指向的值将相同,即稍后分配的值。在值接收器的情况下,接收器是执行时创建的副本defer,因此如果您为变量分配一个新值并调用 another defer,则会创建另一个与前一个不同的副本。

猛跑小猪

有效的围棋提到:延迟函数的参数(如果函数是方法,则包括接收者)在延迟执行时计算,而不是在调用执行时计算。除了避免担心函数执行时变量会改变值,这意味着单个延迟调用站点可以延迟多个函数执行在您的情况下,延迟将引用第二行实例。这两个延迟函数以 LIFO 顺序执行(也如“延迟、恐慌和恢复”中所述)。正如icza在他的回答和评论中提到的:2 个延迟Close()方法将引用2 个不同的 Rows 值,并且两者都将被正确关闭,因为rows是指针,而不是值类型。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go