《GO语言圣经》读书笔记(四):函数

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-nilnil表示函数运行成功。如果想看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比较,不过函数值和函数值之间是不可以比较的,也不能用函数值作为mapkey

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。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值