在Go中有一部分函数总是能成功的运行。比如strings.Contains和strconv.FormatBool函数;对于大部分函数而言,永远无法确保能否成功运行。
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface { Error() string }
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }
这里有一个错误处理的例子:
package main import "errors" import "fmt" // 按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。 func f1(arg int) (int, error) { // errors.New 构造一个使用给定的错误信息的基本error 值。 if arg == 42 { return -1, errors.New("can't work with 42") } // 返回错误值为 nil 代表没有错误。 return arg + 3, nil } // 通过实现 Error 方法来自定义 error 类型是可以的。 // 这里使用自定义错误类型来表示上面的参数错误。 type argError struct { arg int prob string } func (e *argError) Error() string { return fmt.Sprintf("%d - %s", e.arg, e.prob) } func f2(arg int) (int, error) { if arg == 42 { // 在这个例子中,我们使用 &argError 语法来建立一个新的结构体, // 并提供了 arg 和 prob 这个两个字段的值。 return -1, &argError{arg, "can't work with it"} } return arg + 3, nil } func main() { // 下面的两个循环测试了各个返回错误的函数。 // 注意在 if行内的错误检查代码,在 Go 中是一个普遍的用法。 for _, i := range []int{7, 42} { if r, e := f1(i); e != nil { fmt.Println("f1 失败:", e) } else { fmt.Println("f1 工作:", r) } } for _, i := range []int{7, 42} { if r, e := f2(i); e != nil { fmt.Println("f2 失败:", e) } else { fmt.Println("f2 工作:", r) } } // 你如果想在程序中使用一个自定义错误类型中的数据, // 你需要通过类型断言来得到这个错误类型的实例。 _, e := f2(42) if ae, ok := e.(*argError); ok { fmt.Println(ae.arg) fmt.Println(ae.prob) } }
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
示例:
package main import "fmt" import "os" func main() { // 假设我们想要创建一个文件,向它进行写操作,然后在结束时关闭它。 // 这里展示了如何通过 defer 来做到这一切。 f := createFile("D:/defer.txt") // f := createFile("/tmp/defer.txt") // 在 closeFile 后得到一个文件对象, // 我们使用 defer通过 closeFile 来关闭这个文件。 // 这会在封闭函数(main)结束时执行,就是 writeFile 结束后。 defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File) { fmt.Println("writing") fmt.Fprintln(f, "data") } func closeFile(f *os.File) { fmt.Println("closing") f.Close() }
Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。
示例如下:
package main import ( "fmt" "os" ) func main() { // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 panic("一个异常") // panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。 // 这里是一个在创建一个新文件时返回异常错误时的panic 用法。 fmt.Println("继续") _, err := os.Create("/tmp/file") if err != nil { panic(err) } // 运行程序将会引起 panic,输出一个错误消息和 Go 运行时栈信息,并且返回一个非零的状态码。 }
通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态。如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
示例:
package main import ( "fmt" "os" ) func main() { // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 panic("一个异常") // panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。 // 这里是一个在创建一个新文件时返回异常错误时的panic 用法。 fmt.Println("继续") _, err := os.Create("/tmp/file") if err != nil { panic(err) } // 运行程序将会引起 panic,输出一个错误消息和 Go 运行时栈信息,并且返回一个非零的状态码。 }
腾讯课堂教程地址:golang基础教程-快速入门go语言
bilibili教程地址:golang基础教程-快速入门go语言
公众号搜索:GoWeb学习之路