Go中的异常处理(基础)

文章详细介绍了Go语言中错误处理的基本方式,包括使用error接口自定义错误,以及通过defer关键字进行资源清理和异常恢复。此外,还讲解了panic和recover的使用,强调了在遇到不可恢复的错误时使用panic,而在延迟函数中使用recover来捕获并处理panic。
摘要由CSDN通过智能技术生成

Go 中异常处理

主要掌握 一下几个方面:

  1. 掌握error接口
  2. 掌握defer延迟
  3. 掌握panic及recover

error接口

error是指程序中出现不正常的情况,从而导致程序无法正常运行;

go中为错误的类型提供了简单的错误处理机制

go中error的源码:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

可以看到接口中定义了Error方法,我们可以实现这个方法已实现error接口;

error本质上是一个接口类型,其中包含一个Error()方法,错误值可以存储在变量中,通过函数返回。它必须是函数返回的最后一个值。

在go中处理错误的方式通常是以下的形式:

if err!=nil{
fmt.println(err)
}

代码Demo


func Divide(a,b float64)(float64,error){
	if b==0{
		return -1 ,errors.New("by zero")
	}else {
		return a/b, nil
	}

}
func main() {
	divide, err := Divide(10, 0)
	if err!=nil{
		fmt.Println(err)
	}else {
		fmt.Println(divide)
	}
}

在这里插入图片描述

创建error对象

由于error是一个接口,因此只要实现该接口中的方法就可以:

结构体只要实现了Error() string这种格式的方法,就代表实现了该错误接口,返回值为错误的具体描述

在这之前我们先看一下go中提供了哪些关于error的方法:
在这里插入图片描述

简单看一下Join()的源码:

func Join(errs ...error) error {
	n := 0
	for _, err := range errs {
		if err != nil { //如果传进来的err不是 nil
			n++//n自增
		}
	}
	if n == 0 {
		return nil
	}
	e := &joinError{
		errs: make([]error, 0, n),
	}
	for _, err := range errs {
		if err != nil {
			e.errs = append(e.errs, err)
		}
	}
	return e
}

简单来讲就是err的拼接

func main() {
 	r, err := errorR(121)
	if err!=nil {
		fmt.Println(err)
		fmt.Printf("err的类型%T\n",err)
	}else {
		fmt.Println(r)
	}
}

func errorR(age int) (string, error) {

	if age < 0 || age > 120 {
		err := fmt.Errorf("输入的年龄%d不符合统计要求", age)
		return "", err
	} else {
		str := "您输入的年龄" + strconv.FormatInt(int64(age), 10) + "符合要求"
		return str, nil
	}
}

自定义错误

自定义错误的实现步骤如下。

  • • 定义一个结构体,表示自定义错误的类型。
  • • 让自定义错误类型实现error接口:Error()string。
  • • 定义一个返回error的函数。根据程序实际功能而定。

代码demo


//定义结构体

type errorDefine struct {
	reason string
}
//实现Error()方法
func (e errorDefine)Error()string  {

	return "除数为0,不符合要求/"+e.reason
}

func Divide2(a,b int)(float64,error){
	defineerr := errorDefine{reason: "by Zero"}

	if b==0{
		return -1,defineerr
	}else{
		return float64(a / b),nil
	}
}
func main() {
	divide2, err := Divide2(100, 0)
	if err!=nil {
		fmt.Println(err)
	}else {
		fmt.Println(divide2)
	}
}

defer 关键字

defer 是go中一个关键字,用于延迟一个函数或者方法(或者当前创建的匿名函数)的执行;

defer只能出现在函数或者方法的内部

defer的使用

在函数中可以添加多个defer语句。如果有很多调用defer,当函数执行到最后时,这些defer语句会按照逆序执行(报错的时候也会执行),最后该函数返回。

代码Demo

func DeferA(){
	println("这是deferA")
}
func DeferB(){
	println("这是deferB")
}
func DeferC(){
	println("这是deferC")
}

func main() {
	defer DeferA()
	DeferB()
	defer DeferC()

	fmt.Println("打印一些东西")
}

在这里插入图片描述

函数(整体)

defer 经常被用于**处理成对的操作,**如 打开-关闭连接,加锁解锁,等

func Divide(a, b float64) (float64, error) {
	if b == 0 {
		return -1, errors.New("by zero")
	} else {
		return a / b, nil
	}

}

func DeferB() {
	println("这是deferB")
}
func DeferC() {
	println("这是deferC")
}

func DeferFinish() {
	fmt.Println("运算结束")
}
func main() {
	divide, err := Divide(1024, 2)
	if err != nil {
		errors.New("发生了错误,请检查参数")

	}else{
		fmt.Printf("计算结果是%f\n",divide)
	}
	defer DeferFinish()
	DeferC()

	DeferB()
}

运行多次发现,defer 定义的方法DeferFinish 只能保证运行在Divide之后;

在这里插入图片描述

函数调用

以上defer是应用在函数内部,但是defer的使用 并不局限于函数,延迟一个方法的调用用也是可以的;

例如下面的例子:


func (f Fruit) FruitInfo() {
	fmt.Println(f.name, "--", f.color, "--", f.weight)

}

func main() {
	fruit := Fruit{
		name:   "樱桃",
		color:  "红",
		weight: 1.5,
	}

	defer fruit.FruitInfo()

	fmt.Println("我要吃樱桃")
	divide, _ := Divide(1024, 3)
	defer DeferFinish()
	fmt.Println(divide)
}

在这里插入图片描述

函数参数

延迟函数的参数在执行延迟语句时已被执行 而不是在执行实际的函数调用执行 时在执行;

什么意思呢,就是该函数的执行结果只是被延迟显示,换句话讲就是函数的参数已经被传递过来了;

看个例子就知道了;

func addNum(a, b int, flag rune) {

	if flag == 1 {
		fmt.Printf("延迟执行的函数 参数的值%d --%d ,和为 %d\n", a, b, a+b)
	} else {
		fmt.Printf("未延迟执行的函数 参数的值%d --%d ,和为 %d\n", a, b, a+b)

	}

}

func main() {
	a := 10
	b := 18
	flag := '1'
	//延迟调用addNum
	defer addNum(a, b, flag)
	//此时修改a b 的值
	a=100
	b=234
	flag='0'
	//再调用函数,看看被defer的函数的参数是否会受到再次赋值的影响
	addNum(a,b,flag)
}

看结果------

在这里插入图片描述
被延迟执行的参数 依照就近原则,在被defer定义时就已经确定了;

堆栈

当一个函数有多个延迟调用时,他们会被添加到一个堆栈中,按照LIFO的 顺序执行

例如我们要实现一个字符串的逆序输出:

一般的写法如下:

func ReversStr(str string) {
	//转换字节切片
	bytes := []byte(str)

	for i := len(bytes)-1; i >=0 ; i-- {
		fmt.Print(string(bytes[i]))
	}

}

func ReversStr2(str string){
	//转换字节切片
	r := []rune(str)
	for i := 0; i < len(r); i++ {
		defer fmt.Print(string(r[i]))
	}
	println()
}

func main() {
	ReversStr("hello world")
	ReversStr2("hello world")

}

panic和recover机制

go中异常处理机制跟java中不一样,go在设计时,设计者认为将异常于流程控制混在一起会让代码变得混乱;

go中panic()是一个内建的函数,可以中断原有的流程就像java中抛出异常一样;

代码demo

func PanicA() {
	fmt.Println("正常打印")
}
func PanicB() {
	fmt.Println("正常打印B")
	//参数是any 类型的
	panic("发生了异常中断了Panic")
}

func PanicC() {
	fmt.Println("我想要被正常打印")
}

func main() {
	PanicA()
	PanicB()//
	PanicC()

}

运行结果:

正常打印
正常打印B
panic: 发生了异常中断了Panic

goroutine 1 [running]:
main.PanicB(...)
	D:/GolandData/main.go:902
main.main()
	D:/GolandData/main.go:917 +0xa8

Process finished with the exit code 2

panic与error

通常情况下我们使用error就可以了,但是当遇到一些不可恢复的错误状态的时候,比如数组下标越界,空指针引用等,这些会引起panic,

不应通过调用panic()函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式

func SliceAA(i int){
	var s [10]int
	s[i]=100

}
func main() {
	PanicA()
	PanicC()
SliceAA(11)
}

运行结果:

正常打印
我想要被正常打印
panic: runtime error: index out of range [11] with length 10

goroutine 1 [running]:
main.SliceAA(...)
	D:/GolandData/main.go:912
main.main()
	D:/GolandData/main.go:918 +0xa5

Process finished with the exit code 2

recover

panic一旦被引发就会导致程序崩溃,那怎么拦截呢?

go为开发者提供了内建函数recover(),该函数可以让goroutine恢复过来并重新获得流程控制权;

recover()让程序恢复,必须在延迟函数中执行,即 recover()只在延迟函数中生效;

正常的程序运行过程中,调用 recover()返回nil,并且没有其他任何效果
如果当前的Goroutine陷入恐慌,调用recover()可以捕获panic()的输入值,使程序恢复正常运行。

recover使用方式

代码Demo

1,自己的panic自己处理,不向上级抛

func PanicA() {
	fmt.Println("正常打印")
}
func PanicB() {
//自己内部处理
	defer func(){
		if msg:=recover();msg!=nil{
			fmt.Println("恢复中,获取的recover返回值:",msg)
		}
	}()
	fmt.Println("正常打印B")
	for i := 0; i < 10; i++ {
		fmt.Println(i)
		if i==5 {
			panic("有致命panic发生")
		}
	}
	//参数是any 类型的
	//panic("发生了异常中断了Panic")
}

func PanicC() {

	defer func(){
		fmt.Println("延迟执行函数")
		msg:=recover()
		fmt.Println("recover返回值",msg)
	}()
	fmt.Println("我想要被正常打印")
	panic("发生了panic")
}

func main() {
	PanicA()
	PanicB()
	PanicC()
	fmt.Println("over--over")
}

在这里插入图片描述

2,发生panic,谁调用谁处理

func PanicD(){
	fmt.Println("开始打印")

	for i := 0; i < 10; i++ {
		fmt.Println("i--",i)
		if i==5 {
			panic("i=5时发生致命错误")
		}
	}
}

func main() {
	defer func(){
		if msg:=recover();msg!=nil{
			fmt.Println("恢复程序执行,recover()返回值--",msg)
		}
	}()
	PanicD()
}

在这里插入图片描述

注意:这里的恢复执行是指的程序不中断,而不是指可以继续打印5之后的数字;

小结:recover()要在延迟函数中执行,可以专门定义一个延迟函数 来捕获panic 或者使用匿名函数来捕获;

一般情况下我们只需要考虑error,判断err是否为nil 就可以了,很少涉及到panic

代码中尽量少有或者没有panic异常,这是底线呀

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMartain

祝:生活蒸蒸日上!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值