Golang推迟澄清

Kok*_*zzu 17 go

当该方法的结构发生变化时,当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
}
Run Code Online (Sandbox Code Playgroud)

哪个rows最后一个rows.Close()叫?

icz*_*cza 18

它取决于方法接收器变量的类型.

简短的回答:如果你正在使用这个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()
Run Code Online (Sandbox Code Playgroud)

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

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

如果变量不是指针:在调用延迟的方法时,您将观察到不同的结果,具体取决于方法是否具有指针接收器.
如果变量是指针,您将始终看到"所需"的结果.

看这个例子:

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()
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

Go Playground尝试一下.

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

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

非指针变量的说明:

如上所述,在defer执行时评估包括接收器的延迟功能.在指针接收器的情况下,它将是局部变量地址.因此,当您为其分配新值并调用另一个值时defer,指针接收器将再次局部变量的地址相同(只是指向的值不同).因此,稍后当执行该函数时,两者将使用相同的地址两次,但指向的值将是相同的,稍后指定的值.

在值接收器的情况下,接收器是在执行时产生的副本defer,因此如果为变量分配新值并调用另一个defer,则将产生与前一个不同的另一个副本.