go语言异常处理
- 掌握error接口
- 掌握defer延迟
- 掌握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异常