在Go中,关键字 defer 延迟函数的执行,直到外围函数结束。这对于资源的释放和最终处理很方便。然而,将defer与接受指针(*T)或值(T)的方法一起使用时,有一些不明显的细微差别。
出于代码安全性的考虑,Go最初设计为保证无论从函数的哪一点退出都会自动调用清理代码。然而,所使用的接收者类型(指针或值)决定了是否复制调用defer的方法对象或修改原始对象。
如果我们调用一个具有值接收者(T)的方法,则在defer中复制结构的值。如果调用一个具有指针接收者(*T)的方法,则该方法作用于原始对象。因此,在defer方法中通过值修改的数据是不可见的,而通过指针修改的则会反映在外部结构上。这导致难以捕捉的错误,尤其是在尝试通过defer修改对象的状态时。
在设计方法和使用defer时,始终有意识地选择接收者类型。需要持续到函数结束的更改应通过指针进行。
代码示例:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // 值接收者方法 defer func() { c.Value++ // 只有副本会增加 fmt.Println("[值接收者defer] 值:", c.Value) }() } func (c *Counter) IncPointer() { // 指针接收者方法 defer func() { c.Value++ // 原件会增加 fmt.Println("[指针接收者defer] 值:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // 值保持为10 c.IncPointer() // 值变为11 fmt.Println("调用后的原始值:", c.Value) }
关键特性:
1. 如果在defer中调用接受值接收者的方法,外部对象会改变吗?
不会,在defer中原始对象不会改变,因为该方法使用的是结构的副本。
2. 可以依赖defer通过值接收者的方法来保证状态变化吗?
不可以。如果希望反映对原始对象的更改,需要使用指针接收者的方法。
3. 如果在声明defer后更改结构的字段,会怎样?
行为取决于接收者的传递方式:如果方法/函数获取的是副本,则在声明defer后进行的更改不会在延迟函数中显现。
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // 捕获当前值 c.Value = 42 // 不会影响defer }
我们编写了一个关闭连接的函数,通过值接收者的方法在defer中更新状态。结果发现关闭标志没有被更新。
优点:
缺点:
我们使用指针接收者的方法进行最终处理,修改了原始对象。
优点:
缺点: