更改了该方法的结构后,当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()叫什么名字?
rows
rows.Close()
它取决于方法的接收者 和 变量的类型。
简短的答案:如果您使用的是database/sql包,则延迟的Rows.Close()方法将正确关闭两个Rows实例,因为它们Rows.Close()具有 指针 接收器, 并且 由于DB.Query()返回了 指针 ( 指针 也是如此rows)。请参阅下面的推理和解释。
database/sql
Rows.Close()
Rows
DB.Query()
为避免混淆,我建议使用不同的变量,这样会清楚您 想要 什么以及 将要 关闭的内容:
rows := Query(`SELECT FROM whatever`) defer rows.Close() // ... rows2 := Query(`SELECT FROM whatever`) defer rows2.Close()
我想指出一个重要的事实,它来自于deferred函数及其参数被立即评估,这在Effective Go博客文章和Language Spec:Deferred语句中也有说明:
每次执行“ 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执行时会评估包括接收器在内的延迟功能。如果是指针接收器,它将是 局部变量 的 地址 。因此,当您给它分配一个新值并调用另一个值时defer,指针接收器将再次成为局部变量的 相同地址 (只是指向的值不同)。因此,稍后在执行该函数时,两个都将使用相同的地址两次,但 指向的 值将是相同的,后面将分配一个。
defer
对于值接收器,接收器是在执行时创建的 副本defer,因此,如果将新值分配给变量并调用另一个值,则将创建defer另一个副本,该副本与先前的副本不同。