Go中的defer看似很简单,实则一点都不难

Golang 中的 Defer

在Go语言中,defer语句用于将一个函数调用推迟到外围函数返回之后执行。它常用于确保某些操作在函数结束时一定会执行,例如资源释放、文件关闭等。

基本语法

defer语句的基本使用方法如下:

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

输出:

Hello
World

在上面的例子中,fmt.Println("World")defer语句中,所以它会在main函数结束时执行,而不是在定义它的地方立即执行。

多个defer

如果在一个函数中有多个defer语句,它们的执行顺序是后进先出(LIFO)的。也就是说,最后一个defer语句会最先执行。

func main() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
    fmt.Println("Hello")
}

输出:

Hello
Third
Second
First

典型用例

1. 文件操作

在处理文件操作时,可以使用defer确保文件关闭:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()
    
    // 读取文件内容
}

2. 资源释放

defer还可以用于释放其它类型的资源,例如网络连接、数据库连接等:

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 数据库操作
}

3. 锁的解锁

在并发编程中,可以使用defer确保锁在临界区操作完成后被释放:

package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex

func main() {
    mu.Lock()
    defer mu.Unlock()
    
    // 临界区操作
    fmt.Println("Critical section")
}

defer与匿名函数

有时候,我们需要在defer中执行更复杂的操作,此时可以使用匿名函数:

package main

import "fmt"

func main() {
    defer func() {
        fmt.Println("Deferred call")
    }()
    
    fmt.Println("Main function")
}

输出:

Main function
Deferred call

defer与返回值

defer还可以用来修改返回值:

package main

import "fmt"

func test() (result int) {
    defer func() {
        result++
    }()
    return 0
}

func main() {
    fmt.Println(test())  // 输出1
}

在上面的例子中,defer中的匿名函数会在test函数返回之前执行,并修改result的值。

defer 误区

以下,我将以对比的形式展开对 defer 误区的展示。(你可以先自己猜一下每段代码的执行结果)

误区一

func Defer() {
	i := 0
	defer func() {
		println(i)
	}()
	i = 1
}

在这个函数中,defer 语句延迟执行匿名函数,直到 Defer 函数即将返回。

这个匿名函数是一个闭包,它捕获并引用了外部变量 i

因此,当 defer 延迟的匿名函数最终执行时,它打印的是闭包捕获的变量 i 的当前值。

  1. 定义变量 i 并赋值为 0
  2. 定义 defer 延迟执行的匿名函数,它捕获变量 i
  3. 将变量 i 修改为 1
  4. Defer 函数即将返回时,执行 defer 延迟的匿名函数,打印捕获的变量 i 的当前值 1
    因此,Defer 函数的输出是 1
func DeferV1() {
	i := 0
	// 立即调用 defer 延迟的匿名函数,并将当前变量 i 的值传递给它的参数 i。
	defer func(i int) {
		println(i)
	}(i)
	i = 1
}

在这个函数中,defer 语句延迟执行的匿名函数有一个参数 i,并且在调用时将当前的 i 值作为参数传递给匿名函数。

这意味着在 defer 声明时,匿名函数参数 i 的值已经确定,并且与外部变量 i 无关。

  1. 定义变量 i 并赋值为 0
  2. 定义 defer 延迟执行的匿名函数,并立即将当前变量 i(值为 0)传递给它的参数 i
  3. 将外部变量 i 修改为 1
  4. DeferV1 函数即将返回时,执行 defer 延迟的匿名函数,打印参数 i 的值 0

因此,DeferV1 函数的输出是 0

误区二

func DeferReturn() int {
	a := 0
	defer func() {
		a = 1
	}()
	return a
}

在这个函数中,变量 a 是一个局部变量。

return a 语句执行时,defer 语句的执行会在 return 语句之后立即发生,但在实际返回值被传递给调用者之前。

  1. 定义变量 a 并赋值为 0
  2. 设置 defer 延迟执行的匿名函数,将变量 a 修改为 1
  3. 执行 return a,此时返回值为 0
  4. defer 延迟的匿名函数执行,将变量 a 修改为 1,但此时返回值已经确定为 0

因此,DeferReturn 函数的返回值是 0

func DeferReturnV1() (a int) {
	// 全局可见 命名返回值 a
	a = 0
	defer func() {
		a = 1
	}()
	return
}

在这个函数中,变量 a 是一个命名返回值。

在 Go 语言中,当一个函数声明了命名返回值时,该返回值变量会在函数开始时被隐式声明,并且在整个函数体中都是可见的。

因此,当 return 语句执行时,返回的值是命名返回值变量 a 的当前值。

  1. 定义命名返回值变量 a 并隐式初始化为 0
  2. 设置 defer 延迟执行的匿名函数,将变量 a 修改为 1
  3. 执行 return 语句,返回命名返回值变量 a
  4. 在实际返回值被传递给调用者之前,执行 defer 延迟的匿名函数,将变量 a 修改为 1

因此,DeferReturnV1 函数的返回值是 1

误区三

type MyStruct struct {
	name string
}

func DeferReturnV2() *MyStruct {
	a := &MyStruct{
		name: "ypb",
	}

	defer func() {
		a.name = "zmz"
	}()

	return a
}

关键点

  1. 指针修改

a 是一个指向 MyStruct 实例的指针。

defer 延迟的匿名函数中,修改的是 a 指向的对象的 name 字段,而不是指针本身。

这意味着,即使 return a 语句执行时,defer 语句会在实际返回之前执行,修改指针所指向的对象的内容。

  1. defer 的执行顺序

defer 语句会在包含它的函数返回之前执行。

具体来说,defer 延迟的匿名函数在 return 语句设置返回值之后,实际返回之前执行。

因此,匿名函数在返回之前对指针所指向的对象的修改是有效的。

本例子执行顺序:

  1. 创建 MyStruct 实例并赋值给 a,此时 a.name = "ypb"
  2. 设置 defer 延迟执行的匿名函数,准备在函数返回之前执行。
  3. 执行 return a,此时准备返回 a 指向的对象。
  4. 在返回之前,执行 defer 延迟的匿名函数,将 a.name 修改为 "zmz"
  5. 返回 a,此时 a 指向的 MyStruct 实例的 name 字段已经被修改为 "zmz"

defer 自测

只需要自己猜测一下代码的输出结果即可。

很多人误以为在循环中使用 defer 会在每次迭代时执行推迟的操作。实际上,defer 是在函数返回时才执行的,因此在循环中多次使用 defer 会在函数结束时按照后进先出顺序依次执行所有的 defer 语句。

测试一:

// DeferClosureLoop1 函数的输出结果为 十个 10
func DeferClosureLoop1() {
	for i := 0; i < 10; i++ {
		i := i
		defer func() {
			println(i)
		}()
	}
}

测试二:

// DeferClosureLoop2 函数的输出结果为 9 ~ 0
func DeferClosureLoop2() {
	for i := 0; i < 10; i++ {
		defer func(val int) {
			println(val)
		}(i)
	}
}

测试三:

// DeferClosureLoop3 与 DeferClosureLoop2 函数的输出结果相同
func DeferClosureLoop3() {
	for i := 0; i < 10; i++ {
		j := i
		defer func() {
			println(j)
		}()
	}
}

总结

defer在 Go 语言中用于推迟函数调用,直到外围函数返回。

常见用法包括确保资源释放(如文件关闭、解锁)、在多层嵌套函数中统一处理异常等。

defer语句按后进先出顺序执行,支持匿名函数并可修改命名返回值。

需注意变量捕获、循环中使用defer导致堆积等误区。正确使用defer有助于代码清晰与资源管理。

  • 33
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值