defer、异常处理、import

目录:

1:defer延迟执行

2:异常处理

3:import规则

 

一、defer延迟执行

Go语言的 defer 语句会将其后面跟随的语句进行延迟处理

defer特性:

  1. 关键字 defer 用于注册延迟调用。

  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。

  3. 多个defer语句,按先进后出的方式执行。

  4. defer语句中的变量,在defer声明时就决定了。

defer的用途:

  1. 关闭文件句柄

  2. 锁资源释放

  3. 数据库连接释放

go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。

案例1:统一函数执行耗时时间是多少

  • Go 语言中所有的函数调用都是传值的

  • 调用 defer 关键字会立刻拷贝函数中引用的外部参数 ,包括start 和time.Since中的Now

  • defer的函数在压栈的时候也会保存参数的值,并非在执行时取值

解决问题:

package main

import (
	"log"
	"time"
)

func main() {
	start := time.Now()
	log.Printf("开始时间为:%v", start)
	defer func() {
		log.Printf("开始调用defer")
		log.Printf("时间差:%v", time.Since(start))
		log.Printf("结束调用defer")
	}()
	time.Sleep(3 * time.Second)

	log.Printf("函数结束")
}

因为拷贝的是函数指针,函数属于引用传递

案例2:按顺序打印数值

package main

import "fmt"

func main() {
	var whatever = [5]int{1,2,3,4,5}
	for i,_ := range whatever {
        //函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.
		defer func() { fmt.Println(i) }()
	}
}

解决办法:

package main

import "fmt"

func main() {
	var whatever = [5]int{1,2,3,4,5}
	for i,_ := range whatever {
		i := i //将i进行重新赋值,每个i都开辟一个自己的内存空间
		defer func() { fmt.Println(i) }()
	}
}

二、异常处理 

Go语言中使用 panic 抛出错误,recover 捕获错误。

异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

panic:

  1. 内置函数

  2. 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行

  3. 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行

  4. 直到goroutine整个退出,并报告错误

recover:

  1. 内置函数

  2. 用来捕获panic,从而影响应用的行为

golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异常信息,最后退出 goroutine。

如果在 defer 中使用了 recover() 函数,则会捕获错误信息,使该错误信息终止报告。

注意:

  1. 利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。

  2. recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

  3. 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

package main

func main() {
    test()
}

func test() {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // 将 interface{} 转型为具体类型。
        }
    }()

    panic("panic error!")
}

由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。

 func panic(v interface{})
 func recover() interface{}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获:

package main

import "fmt"

func test() {
    defer func() {
        // defer panic 会打印
        fmt.Println(recover())
    }()

    defer func() {
        panic("defer panic")
    }()

    panic("test panic")
}

func main() {
    test()
}

如果需要保护代码段,可将代码块重构成匿名函数,如此可确保后续代码被执 :

package main

import "fmt"

func test(x, y int) {
    var z int

    func() {
        defer func() {
            if recover() != nil {
                z = 0
            }
        }()
        panic("test panic")
        z = x / y
        return
    }()

    fmt.Printf("x / y = %d\n", z)
}

func main() {
    test(2, 1)
}

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态:

type error interface {
    Error() string
}

标准库 errors.Newfmt.Errorf函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

package main

import (
    "errors"
    "fmt"
)

var ErrDivByZero = errors.New("division by zero")

func div(x, y int) (int, error) {
    if y == 0 {
        return 0, ErrDivByZero
    }
    return x / y, nil
}

func main() {
    defer func() {
        fmt.Println(recover())
    }()
    switch z, err := div(10, 0); err {
    case nil:
        println(z)
    case ErrDivByZero:
        panic(err)
    }
}

Go实现类似 try catch 的异常处理:

package main

import "fmt"

func Try(fun func(), handler func(interface{})) {
	defer func() {
		if err := recover(); err != nil {
			handler(err)
		}
	}()
	fun()
}

func main() {
	Try(func() {
		panic("test panic")
	}, func(err interface{}) {
		fmt.Println(err)
	})
}

如何区别使用 panic 和 error 两种方式?

惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。

三、import

 

posted @ 2022-03-02 12:10  你是我的神奇  阅读(138)  评论(0编辑  收藏  举报