5.1 函数声明
函数声明包括函数名、 形式参数列表、 返回值列表( 可省略) 以及函数体。 下面提供了一个函数声明的模板。
func name(parameter-list) (result-list) {
body
}
大概知道模板是啥样的了,就要如法炮制,写出了一个真正的函数,函数hypot
做的事情就是求直接三角形的斜边是多少:
func hypot(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3,4)) // "5"
-
形参和返回值的变量名对于函数调用者而言没有意义
-
函数的形参是实参的拷贝。 对形参进行修改不会影响实参。如果实参包括引用类型, 如指针, slice(切片)、 map、 function、 channel等类型, 实参可能会由于函数的简介引用被修改
-
偶尔一些没有函数体的函数声明,这表示该函数不是以Go实现的,这样的声明定义了函数标识符。
package math func Sin(x float64) float //implemented in assembly language
-
在Go中,一个函数可以返回多个值。使用Go的时候,许多标准库的函数返回一个期望得到的返回值,此外还返回函数出错时的错误信息。
5.2 错误
有一些函数将运行失败看作预期结果,他们往往会返回额外的返回值,用来传递错误信息。额外的返回值可能是个布尔值,通常用ok
表示。
value,ok:=cache.Lookup(key)
if !ok{
//cache[key] does not exist。。。
}
对于有些函数,失败的原因可能不止一种,用户需要多了解错误信息,所以额外的返回值是error
类型。
内置的error
类型是接口类型,接口类型不是nil
就是non-nil
,nil
表示函数运行成功。如果想看error
类型的错误信息,可以通过下面两种方式打印字符串类型的错误信息。
fmt.Println(err)
fmt.Printf("%v",err)
- 函数运行失败时会返回错误信息,这些错误信息被认为是一种预期的值而不是异常,所以这和之前学到的语言可能有所区别,不过go有自己的异常机制,仅仅用来处理未被预料的错误。
5.3 函数值
函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。对函数值的调用类似函数调用。
func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }
f := square
fmt.Println(f(3)) // "9"
f = negative
fmt.Println(f(3)) // "-3"
fmt.Printf("%T\n", f) // "func(int) int"
f = product // compile error: can't assign func(int, int) int to func(int) int
函数的零值是nil
,调用值为nil
的函数会引发panic
错误。函数值可以和nil
比较,不过函数值和函数值之间是不可以比较的,也不能用函数值作为map
的key
。
5.4 Deferred、Panic和Recover
当defer语句被执行时, 跟在defer后面的函数会被延迟执行。 直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行, 不论包含defer语句的函数是通过return正常结束, 还是由于panic导致的异常结束。 你可以在一个函数中执行多条defer语句, 它们的执行顺序与声明顺序相反。
defer语句经常被用于处理成对的操作, 如打开、 关闭、 连接、 断开连接、 加锁、 释放锁。 通过defer机制, 不论函数逻辑多复杂, 都能保证在任何执行路径下, 资源被释放。 释放资源的defer应该直接跟在请求资源的语句后。
-
处理文件的操作或者互斥锁时可以考虑使用
deferred
函数。 -
defer
机制常被用于记录何时进入和退出函数。 -
defer语句中的函数会在return语句更新返回值变量后再执行,可以利用这一点修改函数返回给调用者的返回值。
defer
后面跟的是个匿名函数。func double(x int) int { return x + x } func triple(x int) (result int) { //括号不要少,否则执行不了的! defer func() { result += x }() return double(x) } fmt.Println(triple(4)) // "12"
有些错误只能在运行时检查,比如数组访问越界、空指针引用,这些错误会引起panic
,这里要说明的是,不是所有panic
都是来自于运行时,直接调用内置的panic
函数也会异常,panic
函数的参数可以是任意值。
panic
会导致程序终端运行,并立即执行在该线程中被延迟的函数(就是defer),panic
会打印panic value
和函数调用的堆栈跟踪信息。
如果web服务器遇到不可预料的严重问题,但在崩溃前应该关闭所有链接,不做处理客户端会一直等待,这种时候可以使用Recover。
如果在deferred函数中调用了内置函数recover, 并且定义该defer语句的函数发生了panic异常, recover会使程序从panic中恢复, 并返回panic value。 导致panic异常的函数不会继续运行, 但能正常返回。 在未发生panic时调用recover, recover会返回nil。
其他
-
参数数量可变的函数叫做可变参数函数,典型的例子就是
fmt.Printf
函数,声明这类函数的时候,需要在参数列表的最后一个参数类型之前加上省略号,表示该函数会接收任意数量的该类型参数func sum(vals...int) int { total := 0 for _, val := range vals { total += val } return total }
在函数体中,vals被看作是类型为[] int的切片,调用者隐式的创建一个数组, 并将原始参数复制到数组中, 再把数组的一个切片作为参数传给被调函数。如果原始参数已经是切片类型,在最后一个参数后加上省略符即可。 但是可变参数函数和以切片为参数的函数是不同的!
fmt.Println(sum()) // "0" fmt.Println(sum(3)) // "3" fmt.Println(sum(1, 2, 3, 4)) // "10" values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10"
小小的总结:
panic用于主动抛出错误,recover用来捕获panic抛出的错误。
-
引发
panic
有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。 -
panic
不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer
里面的panic
能够被后续执行的defer
捕获。 -
发生
panic
后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer
语句,然后逐层打印函数调用堆栈,一直到被recover捕获或运行到最外层函数。 -
recover
用来捕获panic
,阻止panic
继续向上传递。recover()
和defer
一起使用,但是defer
只有在后面的函数体内直接被掉用才能捕获panic
来终止异常,否则返回nil
,异常继续向外传递。多个panic只会捕捉第一个:
package main
import "fmt"
func main(){
defer func(){
if err:=recover();err!=nil{
fmt.Println(err)
}
}()
defer func(){
panic("three")
}
defer func(){
panic("two")
}()
panic("one")
}
//output:three
这一小节的开头,提到了书中的一句话:你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反
,下面通过这个例子来感受一下:
package main
import (
"fmt"
)
func main() {
fmt.Println("外层开始")
defer func() {
fmt.Println("外层准备recover")
if err := recover(); err != nil {
fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码
} else {
fmt.Println("外层没做啥事")
}
fmt.Println("外层完成recover")
}()
fmt.Println("外层即将异常")
f()
fmt.Println("外层异常后")
defer func() {
fmt.Println("外层异常后defer")
}()
}
func f() {
fmt.Println("内层开始")
defer func() {
fmt.Println("内层recover前的defer")
}()
defer func() {
fmt.Println("内层准备recover")
if err := recover(); err != nil {
fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容
}
fmt.Println("内层完成recover")
}()
defer func() {
fmt.Println("内层异常前recover后的defer")
}()
panic("异常信息")
defer func() {
fmt.Println("内层异常后的defer")
}()
fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行
}
打印结果:
外层开始
外层即将异常
内层开始
内层异常前recover后的defer
内层准备recover
"内层"-"异常信息"
内层完成recover
内层recover前的defer
外层异常后
外层异常后defer
外层准备recover
外层没做啥事
外层完成recover
- 可以发现位于
panic("异常信息")
之后的语句就没有再去执行了,然后按照逆序执行defer,并逐级往外层函数栈扩散。 - 利用recover去捕捉panic的时候,defer需要在panic之前声明,负责由于panic之后的代码得不到执行,因此也无法recover。