Go语言中没有try catch语句,而是通过函数返回值逐层往上抛。
错误指的是可能出现问题的地方出现了问题。例如:打开一个文件时失败了,这种情况在人们的意料之中。
异常指的是不应该出现问题的地方出现了问题。例如:引用了空指针,这种情况是在人们的意料之外。
错误是业务过程中的一部分,而异常不是。
Go语言引入了内置的error类型表示错误,错误值可以存储在变量中,从函数中返回,error也可以看成一种数据类型。
package main
import (
"fmt"
"os"
)
func main(){
/*
如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值
*/
f, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
return
}
// 根据f进行文件的读或写
fmt.Println(f.Name(), "opened successfully")
}
error是一种内置数据类型,内置的接口:
type error interface {
Error() string
}
其中定义了一个Error
的方法。使用Go语言提供的包构建一个error:
- errors包下的函数:New(), 创建一个error对象
- fmt包下的Errorf()函数:func Errorf(format string, a …interface{}) error
创建实例如下:
// 1. 创建一个error数据
err1 := errors.New("自己创建的一个错误")
fmt.Println(err1)
fmt.Printf("%T\n", err1)
// 2.另一个创建error的方法
err2 := fmt.Errorf("错误的信息码%d", 404)
fmt.Println(err2)
fmt.Printf("%T\n", err2)
例如:
// 设计一个函数:验证年龄是否合法,如果为负数,就返回一个error
func checkAge(age int) error{
if age < 0{
// 返回error
// err := errors.New("年龄不合法")
err := fmt.Errorf("给定的年龄是:%d, 是不合法的", age)
return err
}
fmt.Println("年龄是:", age)
return nil
}
根据断言查看错误类型相关数据:
package main
import (
"fmt"
"os"
)
func main(){
/*
如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值
*/
f, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
// 使用断言 查看错误信息
if ins, ok := err.(*os.PathError); ok{
fmt.Println("1.Op:", ins.Op)
fmt.Println("2.Path:", ins.Path)
fmt.Println("3.Err:", ins.Err)
}
return
}
// 根据f进行文件的读或写
fmt.Println(f.Name(), "opened successfully")
}
还有可以这个例子:
package main
import (
"fmt"
"net"
)
func main() {
// 正常访问一个网址
addr, err := net.LookupHost("www.baidu.com")
fmt.Println(err)
fmt.Println(addr)
// 不能访问一个网址
addr2, err2 := net.LookupHost("www.baidu2.com")
fmt.Println(err2)
fmt.Println(addr2)
// 使用断言查看断言相关错误信息
if ins, ok := err.(*net.DNSError);ok{
if ins.Timeout(){
fmt.Println("操作超时")
}else if ins.Temporary(){
fmt.Println("临时性错误")
}else{
fmt.Println("通常错误")
}
}
}
如果返回的错误是我们关心的错误,我们可以直接比较,然后具体情况具体分析。例如:
package main
import (
"fmt"
"path/filepath"
)
func main() {
// filepath包的Glob函数用于返回与模式匹配的所有文件的名称,当模式出现错误时,该函数返回一个错误ErrBadPattern
files, err := filepath.Glob("[")
if err != nil && err == filepath.ErrBadPattern{
fmt.Println(err)
return
}
fmt.Println("matched files", files)
}
不要忽略一个错误,忽视错误会招致麻烦。
自定义error。 error是一个接口,实现接口方法即可。例如:
package main
import (
"fmt"
"math"
)
/**
自定义错误
*/
func main() {
radius := 4.0
area1, err1 := circleArea(radius)
if err1 != nil{
fmt.Println(err1)
// 断言判断错误类型为areaError,获取错误类型中的属性
if err, ok:=err1.(*areaError);ok{
fmt.Printf("半径是:%.2f", err.radius)
}
return
}
fmt.Println("圆形的面积:", area1)
radius = - 4.0
area2, err2 := circleArea(radius)
if err2 != nil{
fmt.Println(err2)
// 断言判断错误类型为areaError,获取错误类型中的属性
if err, ok:=err2.(*areaError);ok{
fmt.Printf("半径是:%.2f", err.radius)
}
return
}
fmt.Println("圆形的面积:", area2)
}
// 定义一个结构体
type areaError struct {
msg string
radius float64
}
// 实现error接口
func (e *areaError) Error() string{
return fmt.Sprintf("error:半径,%.2f,%s", e.radius, e.msg)
}
func circleArea(radius float64)(float64, error) {
if radius < 0{
return 0, &areaError{msg:"半径非法", radius:radius}
}
return math.Pi*radius*radius, nil
}
进阶:自定义错误以及对错误的处理方法。
package main
import "fmt"
func main() {
length, width := 5.6, 10.2
area, err := rectArea(length, width)
if err!=nil {
fmt.Println(err)
return
}
fmt.Println("矩形的面积为:", area)
length, width = -5.6, -10.2 // 这是赋值,不是定义
area, err = rectArea(length, width)
if err!=nil {
fmt.Println(err)
// 使用断言判断error类型
if err, ok:= err.(*areaError); ok{
if err.lengthNegative(){
fmt.Printf("error:长度,%.2f小于0", err.length)
}
if err.widthNegative(){
fmt.Printf("error:宽度,%.2f小于0", err.width)
}
}
return
}
fmt.Println("矩形的面积为:", area)
}
// 结构体
type areaError struct {
msg string // 错误的描述
length float64 // 发生错误的时候,矩形的长度
width float64 // 发生错误的时候,矩形的宽度
}
func (e *areaError)Error() string {
return e.msg
}
func (e *areaError) lengthNegative() bool {
return e.length < 0
}
func (e *areaError) widthNegative() bool {
return e.width < 0
}
// 计算矩形的面积
func rectArea(length, width float64) (float64, error) {
msg := ""
if length < 0{
msg = "矩形长度小于零"
}
if width < 0{
if msg == ""{
msg = "矩形宽度小于零"
}else{
msg += ", 矩形宽度小于零"
}
}
if msg != ""{
// 有错误,创建对应的错误结构体(对象)
return 0, &areaError{msg:msg, length:length,width:width}
}
return length*width, nil
}
panic()和recover()
golang(go)中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟defer后面的内容,一直等到defer语句的函数执行完毕是,延迟函数defer候命的内容才会被执行,而不管defer语句是通过return正常结束,还是由于panic导致已成结束。
package main
import "fmt"
func main() {
defer func() {
// recover() 返回panic中的参数类型
// 这时说明恐慌了
if msg:= recover() ;msg != nil{
fmt.Println(msg, "程序恢复了") // 执行recover后,程序就可以恢复执行(不报错),类似于catch
// 但是异常后面的程序是不会再运行了
}
}()
funA()
defer myPrint("defer main 3...")
funB()
defer myPrint("defer main 4...")
fmt.Println("main over")
}
func myPrint(s string) {
fmt.Println(s)
}
func funA() {
fmt.Println("一个很普通的函数A")
}
func funB() { // defer 为外围函数
fmt.Println("一个很普通的函数B")
defer myPrint("defer funcB 1...")
for i:=1; i<=10;i++{
fmt.Println("i:", i)
if i == 5{
// 让程序中断
panic("funB恐慌了") // 参数为一个接口,可以接受任意值
// 这时程序后面的内容就不能再执行了
// 注已经被defer的函数还会执行
}
}
defer myPrint("defer funcB 2...")
}
什么时候使用错误,什么时候使用异常需要根据实际情况而定。例如:
- 空指针引用
- 下标越界
- 除数为0
- 不应该出现的分支,比如default
- 输入不应该引起函数错误
等,可使用异常处理。