溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

go的錯(cuò)誤處理

發(fā)布時(shí)間:2020-08-10 00:01:19 來(lái)源:網(wǎng)絡(luò) 閱讀:337 作者:wangchunyi123 欄目:編程語(yǔ)言

一 error接口

        GO語(yǔ)言中的error類型實(shí)際上是抽象Error()方法的error接口

            type error interface{

                    Error()    string

             }

          GO語(yǔ)言使用該接口進(jìn)行標(biāo)準(zhǔn)的錯(cuò)誤處理。

           對(duì)于大多數(shù)函數(shù),如果要返回錯(cuò)誤,大致上都可以定義為如下模式,將error作為多種返回值中的最后一個(gè),但這并非是強(qiáng)制要求:

            func Foo (param int) (n int,err error){

                // .....   

            }

            調(diào)用時(shí)的代碼建議按照如下方式處理錯(cuò)誤情況

            n,err := Foo(0)

            if err != nil {

                //錯(cuò)誤處理

            }else{

                //使用返回值n

             }

        看下面的例子綜合了一下error接口的用法:

        

package main

import(    
     "fmt"
    )
//自定義錯(cuò)誤類型
type ArithmeticError struct {
    error   //實(shí)現(xiàn)error接口
}
//重寫Error()方法
func (this *ArithmeticError) Error() string {
    return "自定義的error,error名稱為算數(shù)不合法"
 }
 
 //定義除法運(yùn)算函數(shù)
func Devide(num1, num2 int) (rs int, err error) { 
   if num2 == 0 { 
       return 0, &ArithmeticError{}
    } else {
       return num1 / num2, nil
    }
}
func main() { 
       var a, b int
    fmt.Scanf("%d %d", &a, &b)

    rs, err := Devide(a, b)
     if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("結(jié)果是:", rs)
    }
}

   

運(yùn)行,輸入?yún)?shù)5 2(正確的情況):

5 2結(jié)果是: 2

 

若輸入5 0(產(chǎn)生錯(cuò)誤的情況):

5 0自定義的error,error名稱為算數(shù)不合法

 

        通過(guò)上面的例子可以看出error類型類似于Java中的Exception類型,不同的是Exception必須搭配throw和catch使用。


二、defer--延遲語(yǔ)句

        在GO語(yǔ)言中,可以使用關(guān)鍵字defer向函數(shù)注冊(cè)退出調(diào)用,即主函數(shù)退出時(shí),defer后的函數(shù)才會(huì)被調(diào)用。

        defer語(yǔ)句的作用是不管程序是否出現(xiàn)異常,均在函數(shù)退出時(shí)自動(dòng)執(zhí)行相關(guān)代碼。(相當(dāng)于Java中的finally )

當(dāng)函數(shù)執(zhí)行到最后時(shí),這些defer語(yǔ)句會(huì)按照逆序執(zhí)行,最后該函數(shù)返回。

例如:

go的錯(cuò)誤處理

package main

import (    "fmt")

func main() {    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

go的錯(cuò)誤處理

 

其執(zhí)行結(jié)果為:

43210

 

defer語(yǔ)句在聲明時(shí)被加載到內(nèi)存(多個(gè)defer語(yǔ)句按照FIFO原則) ,加載時(shí)記錄變量的值,而在函數(shù)返回之后執(zhí)行,看下面的例子:

例子1:defer語(yǔ)句加載時(shí)記錄值

go的錯(cuò)誤處理

func f1() {
    i := 0
    defer fmt.Println(i) //實(shí)際上是將fmt.Println(0)加載到內(nèi)存
    i++    return}
func main() {
    f1()
}

go的錯(cuò)誤處理

 

其結(jié)果顯然是0

例子2:在函數(shù)返回后執(zhí)行

go的錯(cuò)誤處理

func f2() (i int) {    var a int = 1
    defer func() {
        a++
        fmt.Println("defer內(nèi)部", a)
    }()    return a
}
func main() {
    fmt.Println("main中", f2())
}

go的錯(cuò)誤處理

 

其結(jié)果是

 

defer內(nèi)部 2main中 1

 

 

 

例子3:defer語(yǔ)句會(huì)讀取主調(diào)函數(shù)的返回值,并對(duì)返回值賦值.(注意和例子2的區(qū)別)

go的錯(cuò)誤處理

func f3() (i int) {
    defer func() {
        i++
    }()    return 1}
func main() {
    fmt.Println(f3())
}

go的錯(cuò)誤處理

 

其結(jié)果竟然是2.

通過(guò)上面的幾個(gè)例子,自然而然會(huì)想到用defer語(yǔ)句做清理工作,釋放內(nèi)存資源(這樣你再也不會(huì)為Java中的try-catch-finally層層嵌套而苦惱了)

例如關(guān)閉文件句柄:

srcFile,err := os.Open("myFile")
defer srcFile.Close()

 

關(guān)閉互斥鎖:

mutex.Lock()
defer mutex.Unlock()

 

上面例子中defer語(yǔ)句的用法有兩個(gè)優(yōu)點(diǎn):

1.讓設(shè)計(jì)者永遠(yuǎn)也不會(huì)忘記關(guān)閉文件,有時(shí)當(dāng)函數(shù)返回時(shí)常常忘記釋放打開的資源變量。

2.將關(guān)閉和打開靠在一起,程序的意圖變得清晰很多。

下面看一個(gè)文件復(fù)制的例子:

go的錯(cuò)誤處理

package main

import (    
        "fmt"
    "io"
    "os"
    )

func main() {
    copylen, err := copyFile("dst.txt", "src.txt")
        if err != nil { 
               return
    } else {
        fmt.Println(copylen)
    }

}
//函數(shù)copyFile的功能是將源文件sec的數(shù)據(jù)復(fù)制給dst
func copyFile(dstName, srcName string) (copylen int64, err error) {
    src, err := os.Open(srcName)
        if err != nil {
                return
    }
//當(dāng)return時(shí)就會(huì)調(diào)用src.Close()把源文件關(guān)閉
    defer src.Close()
    dst, err := os.Create(dstName)
     if err != nil { 
          return
    }
       //當(dāng)return是就會(huì)調(diào)用src.Close()把目標(biāo)文件關(guān)閉
     defer dst.Close()
     return io.Copy(dst, src)
}

go的錯(cuò)誤處理

 

可以看到確實(shí)比Java簡(jiǎn)潔許多。

三panic-recover運(yùn)行時(shí)異常處理機(jī)制

Go語(yǔ)言中沒(méi)有Java中那種try-catch-finally結(jié)構(gòu)化異常處理機(jī)制,而使用panic()函數(shù)答題throw/raise引發(fā)錯(cuò)誤,

然后在defer語(yǔ)句中調(diào)用recover()函數(shù)捕獲錯(cuò)誤,這就是Go語(yǔ)言的異常恢復(fù)機(jī)制——panic-recover機(jī)制

兩個(gè)函數(shù)的原型為:

func panic(interface{})
//接受任意類型參數(shù) 無(wú)返回值
func recover() interface{}
//可以返回任意類型 無(wú)參數(shù)

 

一定要記住,你應(yīng)當(dāng)把它作為最后的手段來(lái)使用,也就是說(shuō),你的代碼中應(yīng)當(dāng)沒(méi)有,或者很少有panic的東西。這是個(gè)強(qiáng)大的工具,請(qǐng)明智地使用
它。那么,我們應(yīng)該如何使用它呢?

panic()
是一個(gè)內(nèi)建函數(shù),可以中斷原有的控制流程,進(jìn)入一個(gè)令人panic(恐慌即Java中的異常)的流程中。當(dāng)函數(shù)F調(diào)用panic,函數(shù)F的執(zhí)行被中
斷,但是F中的延遲函數(shù)(必須是在panic之前的已加載的defer)會(huì)正常執(zhí)行,然后F返回到調(diào)用它的地方。在調(diào)用的地方,F(xiàn)的行為就像調(diào)用了panic。這一
過(guò)程繼續(xù)向上,直到發(fā)生panic的goroutine中所有調(diào)用的函數(shù)返回,此時(shí)程序退出。異??梢灾苯诱{(diào)用panic產(chǎn)
生。也可以由運(yùn)行時(shí)錯(cuò)誤產(chǎn)生,例如訪問(wèn)越界的數(shù)組。

recover()
是一個(gè)內(nèi)建的函數(shù),可以讓進(jìn)入令人恐慌的流程中的goroutine恢復(fù)過(guò)來(lái)。recover僅在延遲函數(shù)中有效。在正常
的執(zhí)行過(guò)程中,調(diào)用recover會(huì)返回nil,并且沒(méi)有其它任何效果。如果當(dāng)前的goroutine陷入panic,調(diào)用
recover可以捕獲到panic的輸入值,并且恢復(fù)正常的執(zhí)行。

一般情況下,recover()應(yīng)該在一個(gè)使用defer關(guān)鍵字的函數(shù)中執(zhí)行以有效截取錯(cuò)誤處理流程。如果沒(méi)有在發(fā)生異常的goroutine中明確調(diào)用恢復(fù)

過(guò)程(使用recover關(guān)鍵字),會(huì)導(dǎo)致該goroutine所屬的進(jìn)程打印異常信息后直接退出。

這里結(jié)合自定義的error類型給出一個(gè)使用panic和recover的完整例子:

go的錯(cuò)誤處理

package main

import (    
"fmt"
)
//自定義錯(cuò)誤類型
type ArithmeticError struct {
    error
}
//重寫Error()方法
func (this *ArithmeticError) Error() string {
    return "自定義的error,error名稱為算數(shù)不合法"
    }
//定義除法運(yùn)算函數(shù)***這里與本文中第一幕①error接口的例子不同
func Devide(num1, num2 int) int {    
    if num2 == 0 {
        panic(&ArithmeticError{}) 
        //當(dāng)然也可以使用ArithmeticError{}同時(shí)recover等到ArithmeticError類型
    } else {
            return num1 / num2
    }
}
func main() {
    var a, b int
    fmt.Scanf("%d %d", &a, &b)

    defer func() {
             if r := recover(); r != nil {
               fmt.Printf("panic的內(nèi)容%v\n", r)
                 if _, ok := r.(error); ok {
                fmt.Println("panic--recover()得到的是error類型")
            }
            if _, ok := r.(*ArithmeticError); ok {
                fmt.Println("panic--recover()得到的是ArithmeticError類型")
            }
            if _, ok := r.(string); ok {
                fmt.Println("panic--recover()得到的是string類型")
            }
        }
    }()

    rs := Devide(a, b)
    fmt.Println("結(jié)果是:", rs)
}

go的錯(cuò)誤處理

 

其執(zhí)行的結(jié)果為:

使用與上面相同的測(cè)試數(shù)據(jù),輸入5 2得:

5 2結(jié)果是: 2

 

輸入5 0得:

5 0panic的內(nèi)容自定義的error,error名稱為算數(shù)不合法
panic--recover()得到的是error類型
panic--recover()得到的是ArithmeticError類型

 

可見(jiàn)已將error示例程序轉(zhuǎn)換為了Java中的用法,但是在大多數(shù)程序中使用error處理的方法較多。

需要注意的是:defer語(yǔ)句定義的位置 如果defer放在了

 rs := Devide(a, b)語(yǔ)句之后,defer將沒(méi)有機(jī)會(huì)執(zhí)行即:

go的錯(cuò)誤處理

rs := Devide(a, b)
    defer func() {        
    if r := recover(); r != nil {
            fmt.Printf("panic的內(nèi)容%v\n", r)            
            if _, ok := r.(error); ok {
                fmt.Println("panic--recover()得到的是error類型")
            }            if _, ok := r.(*ArithmeticError); ok {
                fmt.Println("panic--recover()得到的是ArithmeticError類型")
            }            if _, ok := r.(string); ok {
                fmt.Println("panic--recover()得到的是string類型")
            }
        }
    }()


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI