码迷,mamicode.com
首页 > 其他好文 > 详细

Go入门(5)——defer 详解

时间:2021-03-29 12:52:57      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:recover   需要   流程   参数   文件流   puts   UNC   open   关闭数据库   

Go入门(5)——defer 详解

关键字defer允许推迟到函数返回之前(或任意位置执行return语句后)才执行某个语句或者函数。return语句同样可以包含一些操作,所以可能存在将语句推迟到return之后的需求。关键字defer的用法类似于面向对象编程语言Java和C#的finally语句块,一般用于释放某些已分配的资源。

Go中,defer担任try catch的工作,需要给予充分注意。

基本介绍

延时调用函数的语法:

defer func_name(param_list)

当一个函数调用前有关键字defer时,这个函数的执行会推迟到包含这个defer语句的函数即将返回前才执行,例如:

package main 

import "fmt"

func main() {
    function1()
}

func function1() {
    fmt.Printf("In function1 at the top\n")
    defer function2()
    fmt.Printf("In function1 at the bottom\n")
}

func function2() {
    fmt.Printf("Function2: Deferred until the end of the calling function")
}

其输出结果为:

In function1 at the top
In function1 at the bottom
Function2: Deferred until the end of the calling function

defer顺序

如果有多个defer调用,则其调用顺序为先进后出,类似于栈。

package main 

import "fmt"

func f() (r int) {
    defer func() {
        r++
    }()
    return 0
}

func main() {
    fmt.Println(f())
}

defer使用场景

关闭文件流

package main 

import (
	"bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    inputFile, inputError := os.Open("input.dat")
    if inputError != nil {
        fmt.Printf("An error occurred on  opening the inputfile\n" + 
                  "Does the file exists?\n" + 
                  "Have you got access to it?\n")
        return  // exit the function on error
    }
    defer inputFile.Close()
    
    inputReader := bufio.NewReader(inputFile)
    for {
        inputString, readerError := inputReader.ReadString(‘\n‘)
        fmt.Printf("The input was: %s", inputString)
        if readerError == io.EOF {
            return 
        }
    }
}

解锁一个加锁的资源

mu.Lock()
defer mu.Unlock()

打印最终报告

printHeader()
defer printFooter()

关闭数据库连接

defer disconnectFromDB()

简洁化代码

合理使用defer可以让代码更加简洁。以下代码模拟了上述4种情况:

package main 

import "fmt"

func main() {
    doDBOperations()
}

func connectToDB() {
    fmt.Println("connected")
}

func disconnectFromDB() {
    fmt.Println("disconnected from db")
}

func doDBOperations() {
    connectToDB()
    fmt.Println("defering the database disconnect")
    defer disconnectFromDB()  // function called here with defer
    fmt.Println("doing some DB operations ...")
    fmt.Println("crash or network error ...")
    fmt.Println("returning from function here")
    return  // terminate the program
    // deferred function excuted here just before actually returning, even if
    // there is a return or abnormal termination before 
}

实现代码追踪

一种基础且十分实用的代码执行追踪方案就是进入和离开某个函数打印相关信息,抽象为下面两个函数:

package main 

import "fmt"

func trace(s string) {
    fmt.Println("entering: ", s)
}
func untrace(s string) {
    fmt.Println("exiting: ", s)
}

func a() {
    trace("a")
    defer untrace("a")
    fmt.Println("in a")
}

func b() {
    trace("b")
    defer untrace("b")
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

运行结果为:

entering:  b
in b
entering:  a
in a
exiting:  a
exiting:  b

记录函数的参数和返回值

package main

import (
	"log"
    "io"
)

func f1(s string) (n int, err error) {
    defer func() {
        log.Printf("f1(%q) = %d, %v", s, n, err)
    }()
    return 7, io.EOF
}

func main() {
    f1("Go")
}

输出为:

2021/03/27 12:23:53 f1("Go") = 7, EOF

捕捉recover panic

panicrecover是Go的两个内置函数,用于处理Go运行时的错误。panic用于主动抛出错误,recover用于捕获panic抛出的错误。

引发panic有两种情形,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。

发生panic后,程序会从调用panic的函数位置或者发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数。

panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panicdefer里的panic能够被后续执行的defer捕获。

recover用来捕获panic,阻止panic继续向上传递。recoverdefer一起使用,但是defer只有在后面的函数体内直接被调用才能捕获panic来终止异常,否则返回nil,异常继续向外传递。

// 捕获失败
defer recover()
defer fmt.Println(recover)
defer func() {
    func() {
        recover()  // 无效,嵌套两层
    }()
}()

// 捕获成功
defer func() {
    recover()
}()

func except() {
    recover()
}

func test() {
    defer except()
    panic("runtime error")
}

多个panic只会捕捉最后一个

package main 

import "fmt"

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    defer func() {
        panic("three")
    }()
    defer func() {
        panic("two")
    }()
    panic("one")
}

一般在以下情况中用到:

  • 程序遇到无法执行下去的错误,抛出错误,主动结束运行。
  • 在调试程序时,通过panic打印堆栈,方便定位错误。

Go入门(5)——defer 详解

标签:recover   需要   流程   参数   文件流   puts   UNC   open   关闭数据库   

原文地址:https://www.cnblogs.com/4thrun/p/Golang_5.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!