defer语句 #

一、defer概述 #

defer语句将函数调用推迟到外层函数返回之前执行。

二、基本用法 #

2.1 基本语法 #

go
defer 函数调用

2.2 示例 #

go
func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}
// 输出:
// hello
// world

2.3 资源释放 #

go
func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()  // 确保文件关闭
    
    // 处理文件...
    return nil
}

三、执行顺序 #

3.1 后进先出(LIFO) #

多个defer按后进先出顺序执行:

go
func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
}
// 输出:
// 3
// 2
// 1

3.2 defer栈 #

go
func stack() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
// 输出:
// 2
// 1
// 0

四、defer与参数 #

4.1 参数立即求值 #

defer语句中的参数在声明时求值:

go
func main() {
    i := 0
    defer fmt.Println(i)  // 此时i=0
    i++
    fmt.Println(i)  // 1
}
// 输出:
// 1
// 0

4.2 闭包延迟求值 #

使用闭包可以延迟求值:

go
func main() {
    i := 0
    defer func() {
        fmt.Println(i)  // 延迟到执行时求值
    }()
    i++
    fmt.Println(i)
}
// 输出:
// 1
// 1

4.3 方法接收者 #

go
type Counter struct {
    value int
}

func (c *Counter) Print() {
    fmt.Println(c.value)
}

func main() {
    c := &Counter{value: 0}
    defer c.Print()  // 参数c立即求值
    c.value = 10
}
// 输出: 10

五、defer与返回值 #

5.1 命名返回值 #

defer可以修改命名返回值:

go
func example() (result int) {
    defer func() {
        result++
    }()
    return 10
}

fmt.Println(example())  // 11

5.2 执行时机 #

defer在return之后、函数返回之前执行:

go
func example() (result int) {
    defer func() {
        fmt.Println("defer:", result)
        result++
    }()
    result = 10
    fmt.Println("before return")
    return result
}

fmt.Println("result:", example())
// 输出:
// before return
// defer: 10
// result: 11

5.3 匿名返回值 #

匿名返回值不能被defer修改:

go
func example() int {
    var result int
    defer func() {
        result++  // 修改的是局部变量
    }()
    return result  // 返回的是result的副本
}

fmt.Println(example())  // 0

六、实际应用 #

6.1 文件操作 #

go
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    return io.ReadAll(file)
}

6.2 数据库连接 #

go
func queryUser(db *sql.DB, id int) (*User, error) {
    rows, err := db.Query("SELECT * FROM users WHERE id = ?", id)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var user User
    if rows.Next() {
        err = rows.Scan(&user.ID, &user.Name, &user.Email)
        return &user, err
    }
    return nil, sql.ErrNoRows
}

6.3 锁释放 #

go
var mu sync.Mutex

func safeOperation() {
    mu.Lock()
    defer mu.Unlock()
    
    // 安全操作...
}

6.4 计时 #

go
func measureTime(name string) func() {
    start := time.Now()
    return func() {
        fmt.Printf("%s took %v\n", name, time.Since(start))
    }
}

func main() {
    defer measureTime("main")()
    
    time.Sleep(time.Second)
}

6.5 错误处理 #

go
func doSomething() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    
    // 可能panic的操作
    return nil
}

6.6 HTTP响应体关闭 #

go
func fetch(url string) ([]byte, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

七、defer性能 #

7.1 defer开销 #

defer有少量性能开销,但在大多数情况下可以忽略:

go
// 有defer
func withDefer() {
    defer func() {}
}

// 无defer
func withoutDefer() {
}

7.2 优化建议 #

在性能关键的热点代码中,可以考虑避免使用defer:

go
// 使用defer
func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()
    
    // 处理...
    return nil
}

// 不使用defer(性能优化)
func processFileOptimized(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    
    // 处理...
    
    f.Close()  // 手动关闭
    return nil
}

八、常见错误 #

8.1 defer在循环中 #

go
// 错误:资源不会及时释放
func processFiles(paths []string) error {
    for _, path := range paths {
        f, err := os.Open(path)
        if err != nil {
            return err
        }
        defer f.Close()  // 所有文件在函数结束时才关闭
        
        // 处理文件...
    }
    return nil
}

// 正确:使用闭包
func processFiles(paths []string) error {
    for _, path := range paths {
        if err := processFile(path); err != nil {
            return err
        }
    }
    return nil
}

func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()
    
    // 处理文件...
    return nil
}

8.2 nil defer #

go
func example() {
    var f func()
    defer f()  // panic: nil function
}

8.3 defer参数求值时机 #

go
func example() {
    i := 0
    defer fmt.Println(i)  // 输出0,不是1
    i = 1
}

九、最佳实践 #

9.1 资源获取后立即defer #

go
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()  // 立即defer

9.2 使用defer处理错误 #

go
func doSomething() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    
    // 可能panic的代码
    return nil
}

9.3 避免在循环中使用defer #

go
// 不好
for _, item := range items {
    mu.Lock()
    defer mu.Unlock()  // 所有锁在函数结束时释放
    process(item)
}

// 好
for _, item := range items {
    func() {
        mu.Lock()
        defer mu.Unlock()
        process(item)
    }()
}

十、总结 #

defer要点:

特性 说明
执行时机 函数返回前执行
执行顺序 后进先出(LIFO)
参数求值 声明时立即求值
命名返回值 可以修改命名返回值

关键点:

  1. 延迟执行:defer在函数返回前执行
  2. LIFO顺序:多个defer按后进先出执行
  3. 参数求值:参数在声明时求值
  4. 资源释放:常用于关闭文件、释放锁等
  5. 命名返回值:defer可以修改命名返回值

准备好学习递归函数了吗?让我们进入下一章!

最后更新:2026-03-26