Golang07-go语言异常处理

214 阅读3分钟

go语言异常处理

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

error

当foo.txt不存在时候,则报错

package main

import (
	"fmt"
	"io/ioutil"
)

func main()  {
	file,err:=ioutil.ReadFile("foo.txt")
	if err!=nil{
		fmt.Println(err)
		return
	}
	fmt.Println("%s",file)
}
/*
open foo.txt: The system cannot find the file specified.
*/

1.1 error接口

error是指程序中出现不正常的情况,从而导致程序无法正常运行。比如,打开一个文件,文件系统中这个文件不存在,这就是一种error的异常情况,它表示一种错误。

下面就是Error接口

    type error interface{
        Error() string
    }
//异常处理
	err:=fmt.Errorf("a height of %.2f is invalid",-2.3333)
	fmt.Println(err.Error())  //这里返回a height of -2.33 is invalid
	fmt.Println(err)   //这里返回a height of -2.33 is invalid

1.2 创建并打印错误

package main

import (
	"errors"
	"fmt"
)

func main()  {
	err:=errors.New("somthing went wrong!")
	if err!=nil{
		fmt.Println(err)    //返回somthing went wrong!
	}
}

在go语言errors包中就通过New()函数返回error对象。

1.3 设置错误的格式

除了errors包外,标准库中的fmt包还提供了Errorf方法,可用于设置返回的错误字符串的格式。如下面例子,这能狗让您将多个值合并成更有意义的错误字符串,从而动态地创建错误字符串

package  main

import (
	"errors"
	"fmt"
)

func checkAge(age int)(string,error)  {
	if age<0{
		err:=fmt.Errorf("您输入的年龄是:%d,该值为负数,有错误!",age)
		return "",err
	}else{
		return fmt.Sprintf("您输入的年龄是:%d",age),nil
	}
}

func main()  {
	//创建error对象的方式1
	err1:=errors.New("自己创建的错误!")
	fmt.Println(err1)
	fmt.Printf("err1的类型和:%T\n",err1)
	fmt.Println("-------------------")
	//创建error对象的方式2
	err2:=fmt.Errorf("错误的类型%d",10)
	fmt.Println(err2.Error())
	fmt.Println(err2)
	fmt.Printf("Err2的类型:%T\n",err2)
	fmt.Println("--------------------")
	//error对象在函数中的使用
	res,err3:=checkAge(-12)
	if err3!=nil{
		fmt.Println(err3.Error())
		fmt.Println(err3)
	}else{
		fmt.Println(res)
	}
}
/*
输出结果:
自己创建的错误!
err1的类型和:*errors.errorString
-------------------
错误的类型10
错误的类型10
Err2的类型:*errors.errorString
--------------------
您输入的年龄是:-12,该值为负数,有错误!
您输入的年龄是:-12,该值为负数,有错误!
*/

1.4自定义error

package main

import (
	"fmt"
	"time"
)

//定义结构体
type myError struct {
	when time.Time
	what string
}

//实现Error()方法
func (e myError) Error() string {
	return fmt.Sprintf("%v:%v",e.when,e.what)
}

//定义函数,返回error对象。该函数求矩形面积

func getArea(width,length float64)(float64,error)  {
	errInfo:=""
	if width<0&&length<0 {
		errInfo=fmt.Sprintf("长度:%v,宽度:%v,均为负数",length,width)
	}else if length<0{
		errInfo=fmt.Sprintf("宽度:%v,出现次数",width)
	}
	if errInfo!=""{
		return 0,myError{time.Now(),errInfo}
	}else{
		return width*length,nil
	}
}

func test(res float64,err error)  {
	if err!=nil{
		fmt.Printf(err.Error())
	}else{
		fmt.Println("面积为:",res)
	}
}
func main()  {
	res,err:=getArea(-4,-5)
	ret,err2:=getArea(4,5)
	test(res,err)
	fmt.Printf("\n---------------------------------------\n")
	test(ret,err2)
}
/*
返回结果:
2021-07-15 15:43:56.6362444 +0800 CST m=+0.008809801:长度:-5,宽度:-4,均为负数
---------------------------------------
面积为: 20
*/

defer关键字

关键字defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。defer语句只能出现在函数或者方法内部

2.1 函数中使用defer

在函数中使用

package main

import "fmt"

func funcA()  {
	fmt.Println("this is FuncA")
}
func funcB()  {
	fmt.Println("this is FuncB")
}
func funcC()  {
	fmt.Println("this is FuncC")
}

func main()  {
	defer funcA()    //这里是放进延迟栈里面
	funcB()
	defer funcC()    //这里是放进延迟栈里面
	fmt.Println("main over")
}
/*
返回结果
this is FuncB
main over
this is FuncC
this is FuncA
*/

defer语句经常被杨宏宇处理成对的操作,比如打开-关闭、连接-断开、加锁-释放锁。特别是在执行打开资源的操作时,容易遇到错误需要提前返回,在返回前需要关闭相应的资源,不然很容易造成资源泄露等。

defer 查找最大值

package main

import "fmt"

func finished()  {
	fmt.Println("结束!")
}


func getMax(s [] int)  {
	defer  finished()
	fmt.Println("开始寻找最大值:")
	max:=s[0]
	for _,v:=range s{
		if v>max{
			max=v
		}
	}
	fmt.Printf("%v中最大值为:%v\n",s,max)
}

func main()  {
	s1:=[]int{78,456,10,22,123}
	getMax(s1)
}
/*
输出结果:
开始寻找最大值:
[78 456 10 22 123]中最大值为:456
结束!
*/

2.2方法中使用defer

延迟不局限于函数,延迟一个方法调用也是完全合法的。

package main

import "fmt"

type policeMan struct {
	firstName,lastName string
}

func (p policeMan) fullName()  {
	fmt.Printf("%s,%s",p.firstName,p.lastName)
}


func main()  {
	p:=policeMan{"Jack","Ma"}
	defer p.fullName()
	fmt.Println("Hello,World!")
}

/*
返回结果:
Hello,World!
Jack,Ma
*/

2.3defer的参数

package main

import "fmt"

func printAdd(a,b int,bol bool)  {
	if(bol){
		fmt.Println("this is deferFunction:")
		fmt.Printf("%d\n",a+b)
	}else{
		fmt.Println("this is not deferFunction:")
		fmt.Printf("%d\n",a+b)
	}
}
func main()  {
	a:=5
	b:=6
	defer printAdd(a,b,true)  //把这步放进栈里面
	a=10
	b=7
	printAdd(a,b,false)
}
/*
输出结果:
this is not deferFunction:
17
this is deferFunction:
11
*/

2.4 堆栈的推迟

当一个函数有多个defer调用时候,它们就被添加到一个堆栈中,并按照后进先出(Last In First Out,LIFO)的顺序来执行

Sample利用defer实现字符串倒序

package main

import "fmt"

func reverseStr(str string)  {
	for _,v:=range[] rune(str){
		defer fmt.Printf("%c",v)
	}
}


func main()  {
	str:="JackMa,欢迎来学习Golang语言,123开始吧!"
	fmt.Println("原始字符串:",str)
	fmt.Println("反转过后字符串")
	reverseStr(str)
}
/*
原始字符串: JackMa,欢迎来学习Golang语言,123开始吧!
反转过后字符串
!吧始开321,言语gnaloG习学来迎欢,aMkcaJ
*/

上面这样在for中用defer的确可以输出出逆置的字符串,但是存在一个问题就是编译器提示有可能在for中用defer会导致内存泄漏。

panic和recover机制

Golang追求简洁优雅,Go并没有像Java和C#那样用 try..catch..finaly来进行异常处理。Go语言设计者认为,把异常和流程控制混在一起会让代码非常混乱(MeToo我也是这样认为的,之前维护C#项目的时候,遇到问题找不出来Bug直接try..catch..然后xx就说,你学会了try...catch...大法)

panic,让当前的程序进入恐慌,中断程序的执行。panic()是一个内建函数,可以中断原有的控制流程。

Sample1 panic示例1

package main

import "fmt"

func TestA(){
	fmt.Println("func TestA")
}

func TestB()  {
	panic("这里中断了func TestB():panic")
}
func TestC()  {
	fmt.Println("func TestC")
}

func main()  {
	TestA()
	TestB()  //TestB()发生异常,中断程序
	TestC()
}
/*
返回
func TestA
panic: 这里中断了func TestB():panic
*/

recover

panic异常一旦引发就会导致奔溃。这当然不是程序员愿意看到的,但谁也不能保证程序不会发生任何运行时的错误。不过,go语言为开发者提供了专用于“拦截”运行时panic的内建函数recover()

开发中尽量少用panic异常