• 9.Go语言-函数


    1.函数

    • Go语言中支持函数,匿名和闭包,并且函数在Go语言中属于“一等公民”

    • 特点:

      • 无需声明原型。
      • 支持不定 变参。
      • 支持多返回值。
      • 支持命名返回参数。 
      • 支持匿名函数和闭包。
      • 函数也是一种类型,一个函数可以赋值给变量。
      
      • 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
      • 不支持 重载 (overload) 
      • 不支持 默认参数 (default parameter)。
      

    1函数的定义

    • 函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。函数可以没有参数或接受多个参数。(注意类型在变量名之后 )当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。函数可以返回任意数量的返回值。

    • Go语言中定义函数使用func 关键字,具体格式如下:

      func 函数名(参数)(返回值){
      	函数体
      }
      
    • 其中:

      • 函数名:由字母,数字,下划线组成,但函数名的第一个字母不能是数字,在同一个包内,函数名称不能重名
      • 参数:参数由参数变量和参数变量的类型,多个参数之间使用,分隔
      • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值,必须用()包裹,并用分隔。
      • 函数体:实现指定功能的代码块。
    • 定义一个简单函数

      package main
      
      import "fmt"
      
      //函数
      //定义一个不需要参数也没有返回值的函数:sayHello
      
      func sayHello() {
      	fmt.Println("hello xjk")
      }
      
      func main() {
      	//函数调用
      	sayHello()
      }
      
    • 函数作为参数传递,可以将复杂签名定义为函数类型,以便于阅读

      package main
      
      
      import "fmt"
      
      
      func test(fn func() int) int {
      	return fn()
      }
      
      // 定义函数类型
      type FormatFunc func(s string,x,y int) string
      
      // format接收fn 的类型为为一个函数类型,通过type定义一个函数类型更直观
      func format(fn FormatFunc,s string, x,y int) string {
      	return fn(s,x,y)
      }
      
      func main() {
      	// test 内部为一个匿名函数
      	s1 := test(func() int { return 100})//直接将匿名函数当参数
      	// 第一个参数为匿名函数,定义接收类型,返回类型和函数逻辑。
      	// 第二个参数 "%d,%d"  字符串
      	// 第三个,第四个 参数为数字
      	s2 := format(func(s string, x, y int) string {
      		return fmt.Sprintf(s, x, y)
      }, "%d, %d", 10, 20)
      
      	println(s1,s2)
          // 100 10, 20
      }
      

    2.函数参数

    • 函数定义时指出,函数定义时有参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。

      但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:

      • 值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

        func swap(x,y int) int {
        	... ...
        }
        
      • 引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

        func swap(x,y *int){
        	*x,*y = *y,*x
        }
        func main() {
        	var a, b int = 1, 2
        	swap(&a, &b)
        	fmt.Println(a, b)
        }
        // 2 1
        
    • 在默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

    • 注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

    • 注意2:map、slice、chan、指针、interface默认以引用的方式传递。

    • 不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)

      Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

      在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。

        func myfunc(args ...int) {    //0个或多个参数
        }
      
        func add(a int, args…int) int {    //1个或多个参数
        }
      
        func add(a int, b int, args…int) int {    //2个或多个参数
        }
      

      注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数。

      任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。

      用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。

      func myfunc(args ...interface{}) {
        }
      
      • demo
      func test(s string, n...int) string {
      	var x int
      	for _,i := range n{
      		x += i
      	}
      	return fmt.Sprintf(s,x)
      }
      
      func main() {
      	println(test("sum:%d",1,2,3))
      }
      // sum:6
      
    • 使用 slice 对象做变参时,必须展开。(slice...)

      func test(s string, n...int) string {
      	var x int
      	for _,i := range n{
      		x += i
      	}
      	return fmt.Sprintf(s,x)
      }
      
      func main () {
      	s := []int{1,2,3}
      	// s... 将slice展开
      	res := test("sum:%d",s...)
      	println(res)
      }
      

    3.函数返回值

    • 可以用"_"标识符,用来忽略函数的某个返回值。Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。

    • demo

      func add(a,b int) (c int) {
      	c = a + b
      	return
      }
      
      // 指定返回的sum和avg类型,所以return 后面可以不接变量
      func calc(a,b int) (sum int,avg int) {
      	sum = a + b
      	avg = (a + b) / 2
      	return
      }
      
      func main() {
      	var a,b int = 1,2
      	c := add(a,b)
      	sum,avg := calc(a,b)
      	fmt.Println(a,b,c,sum,avg)
      }
      // 1 2 3 3 1
      
    • Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 "_" 忽略。

      func test() (int, int) {
      	return 1,2
      }
      func main() {
      	x,_ := test()
      	println(x)//1
      }
      
    • 多返回值可直接作为其他函数调用实参。

      func test()(int,int) {
      	return 1,2
      }
      
      func add(a,b int) int {
      	return a+b
      }
      
      func sum(n ...int) int{
      	var x int
      	for _,i := range n{
      		x += i
      	}
      	return x
      }
      
      func main() {
      	fmt.Println(add(test()))//3
      	fmt.Println(sum(test()))//3
      }
      
    • 命令返回参数可看作与形参类似的局部变量,最后由return隐式返回。

      package main
      func add(x,y int) (z int) {
          z = x + y
          return
      }
      func main() {
          println(add(1,2))//3
      }
      
    • 命名返回参数可被同名局部变量遮蔽,此时需要显示返回。

      func add(x,y int)(z int){
      	// var z不能再一个级别重复定义,会引发"z redeclared in this block" 错误
      	var z = x+y
      	// 必须显式返回
      	return z
      }
      
    • 命名返回参数允许defer延迟调用通过闭包读取和修改

      func add(a,b int) (c int) {
      	defer func() {
      		c += 100
      	}()
      	c = a + b
      	return
      }
      
      func main() {
          println(add(1, 2)) //103
      }
      
    • 显示return返回前,会先修改命名返回参数。

      func add(x, y int) (z int) {
      	defer func() {
      			println("defer:",z) // 输出: 203
      	}()
      
      	z = x + y
      	return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
      }
      func main() {
      	println(add(1,2))
      }
      // defer: 203
      // 203
      

    4.匿名函数

    • 匿名函数是指不需要定义函数名的一种函数实现方式。1958年LISP首先采用匿名函数。

      在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。

      匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

      package main
      
      import (
      	"fmt"
      	"math"
      )
      
      
      func main() {
      	getSqrt := func(a float64) float64 {
              // 开平方根
      		return math.Sqrt(a)
      	}
      	fmt.Println(getSqrt(4))//2
      }
      

      上面先定义了一个名为getSqrt 的变量,初始化该变量时和之前的变量初始化有些不同,使用了func,func是定义函数的,可是这个函数和上面说的函数最大不同就是没有函数名,也就是匿名函数。这里将一个函数当做一个变量一样的操作。

    • Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。

      package main
      
      import (
      	"fmt"
      	// "math"
      )
      
      
      func main() {
      	// 复制变量通过() 调用
      	fn := func() { fmt.Println("Hello World")}
      	fn()
      	// 切片定义多个函数
      	fn2 := [](func(x int) int){
      		func(x int) int {return x+1},
      		func(x int) int {return x+2},
      	}
      	// 通过索引调用函数
      	println(fn2[0](100))
      	// 结构体内定义函数 
      	d := struct {
      		fn func() string
      	}{
      		fn: func() string {return "hello world!"},
      	}
      	// 通过点语法调用
      	println(d.fn())
      	// 再channel里传送
      	fc := make(chan func() string,2)
      	fc <- func() string {return "Hello,Go!"}
      	println((<-fc)())
      }
      // Hello World
      // 101
      // hello world!
      // Hello,Go
      

    5.闭包与递归

    • 由函数及其相关引用环境组合而成的实体(闭包=函数+引用环境)

    • Go语言支持闭包的,这里只是简单地讲一下在Go语言中闭包是如何实现的

      package main
      import "fmt"
      
      
      func a() func() int{
      	i:= 0
      	b := func() int {
      		i++
      		fmt.Println(i)
      		return i
      	}
      	return b
      }
      
      
      func main() {
      	c := a()
      	c()//1
      	c()//2
      	// c = a() 调用a() 不会输出 i 的值
      }
      
    • 因闭包复制时原对象指针,这样很容易解释延迟引用现象。

      func test() func() {
      	x := 100
      	fmt.Printf("x (%p) = %d
      ", &x, x)
      	return func() {
      		fmt.Printf("inner x (%p) = %d
      ", &x, x)
      	}
      }
      func main() {
      	f := test()
      	f()
      	// x (0x1100a090) = 100
      	// inner x (0x1100a090) = 100
      }
      

      在汇编层 ,test 实际返回的是 FuncVal 对象,其中包含了匿名函数地址、闭包对象指针。当调 匿名函数时,只需以某个寄存器传递该对象即可。

      FuncVal { func_address, closure_var_pointer ... }
      
    • 外部引用函数参数局部变量。

      package main
      
      import "fmt"
      
      // 外部引用函数参数局部变量
      func add(base int) func(int) int {
          return func(i int) int {
              base += i
              return base
          }
      }
      
      func main() {
          tmp1 := add(10)
          fmt.Println(tmp1(1), tmp1(2))
          // 此时tmp1和tmp2不是一个实体了
          tmp2 := add(100)
          fmt.Println(tmp2(1), tmp2(2))
      }
      
    • 返回2个闭包

      func test(base int) (func(int) int, func(int) int ){
      	add := func(i int) int {
      		base += i
      		return base
      	}
      	sub := func(i int) int {
      		base -= i
      		return base
      	}
      	return add,sub
      }
      
      
      
      func main() {
      	f1,f2 := test(1)
      	// base一直是没有消
          // f1执行完base为11,f2执行会用base=11在函数进行运算,所以为-4
      	fmt.Println(f1(10),f2(15))// 11 -4
      }
      
    • 递归函数

      • 递归,就是在运行的过程中调用自己。 一个函数调用自己,就叫做递归函数。

      • 条件:

        1.子问题须与原始问题为同样的事,且更为简单。
        2.不能无限制地调用本身,须有个出口,化简为非递归状况处理。
        
      • 数字阶乘

        • 一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
        package main
        
        import "fmt"
        
        
        func factorial(i int) int {
        	if i<=1{
        		return 1
        	}
        	return i * factorial(i-1)
        }
        
        func main() {
        	var i int = 10
        	fmt.Printf("Factorial of %d is %d
        ",i,factorial(i))// Factorial of 10 is 3628800
        }
        
      • 斐波那契数列

        func fibonaci(i int) int {
        	if (i ==0) {
        		return 0
        	}
        	if (i == 1) {
        		return 1
        	}
        	return fibonaci(i-1) + fibonaci(i-2)
        }
        
        func main() {
        	var i int
            for i = 0; i < 10; i++ {
                fmt.Printf("%d
        ", fibonaci(i))
            }
        }
        // 0
        // 1
        // 1
        // 2
        // 3
        // 5
        // 8
        // 13
        // 21
        // 34
        

    6.延迟调用 defer

    • defer特性:

      1.关键字 defer 用于注册延迟调用
      2.这些调用直到return 前才被执行,因此可以用来做资源清理。
      3.多个defer语句,按先进后出的方式执行。
      4.defer语句中的变量,在defer声明时就决定了。
      
    • defer用途:

      1.关闭文件句柄
      2.锁资源释放
      3.数据库连接释放
      
    • Go语言中defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆顺序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句最先被执行。

      package main
      
      
      import "fmt"
      
      
      func main() {
      	var w [5] struct{}
      	for i := range w{
      		defer fmt.Println(i)
      	}
      }
      // 4
      // 3
      // 2
      // 1
      // 0
      
    • defer碰上闭包

      package main
      
      
      import "fmt"
      
      
      func main() {
      	var w [5] struct{}
      	for i := range w{
      		defer func() {fmt.Println(i)}()
      	}
      }
      // 4
      // 4
      // 4
      // 4
      // 4
      

      函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.

    • defer f.Close 存在陷阱

      type Test struct {
      	name string
      }
      
      func (t *Test) Close() {
      	fmt.Println(t.name," closed")
      }
      
      
      func main() {
      	ts := []Test{{"a"},{"b"},{"c"}}
      	for _,t := range ts {
      		defer t.Close()
      	}
      }
      // c  closed
      // c  closed
      // c  closed
      

      上面代码不如我们预期输出的c b a 而是c c c

    • 可是按照前面go sepc中说明应该c b a 才对,那么换一种方式调用

      type Test struct {
      	name string
      }
      
      func (t *Test) Close() {
      	fmt.Println(t.name," closed")
      }
      func Close(t Test){
      	t.Close()
      }
      
      func main() {
      	ts := []Test{{"a"},{"b"},{"c"}}
      	for _,t := range ts {
      		defer Close(t)
      	}
      }
      // c  closed
      // b  closed
      // a  closed
      

      Go语言中并没有把这个struct的this指针做相应处理,没有明确写出来的this指针当作参数看待。而通过定义Close(t Test) 函数讲当前循环struct传入禁区,然后Test结构体内的方法。

    • 多个defer注册,按FILO次序(先进后出),最后哪怕函数或某个延迟调用发生错误,也会调用依旧被执行。

      func test(x int) {
      	defer println("a")
      	defer println("b")
      	defer func() {
      		println(100/x)// 这里传入0,会报错,异常未被捕获,逐步往外传递,最终终止进程。
      	}()
      	defer println("c")
      }
      
      func main() {
      	test(0)
      }
      // c
      // b
      // a
      // panic: runtime error: integer divide by zero
      
    • 延迟调用参数在注册时求值或复制,可用指针或闭包“延迟”读取。

      package main
      
      
      import "fmt"
      
      
      func test() {
      	x,y := 10,20
      	defer func(i int) {
      		fmt.Println("defer:",i,y)// y在内层函数,引用外层变量
      	}(x) //此时x为值拷贝
      
      	x+=10
      	y+=100
      	fmt.Println("x=",x,"y=",y)
      }
      
      func main() {
      	test()
      }
      // x= 20 y= 120
      // defer: 10 120
      
    • 滥用defer可能会导致性能问题,尤其是在大循环里:

      package main
      
      import (
      	"fmt"
      	"sync"
      	"time"
      )
      
      // 声明  sync.Mutex互斥锁
      var lock sync.Mutex
      
      
      func test() {
          // 加锁
      	lock.Lock()
          // 解锁
      	lock.Unlock()
      }
      
      func testdefer() {
      	lock.Lock()
      	defer lock.Unlock()
      }
      
      
      func main() {
      	func() {
      		t1 := time.Now()
      		for i:=0;i<100000;i++ {
      			test()
      		}
      		// 计算程序运行时间
      		elapsed := time.Since(t1)
      		fmt.Println("test elapsed: ",elapsed)
      	}()
      	func() {
      		t1 := time.Now()
      		for i:=0;i<100000;i++ {
      			testdefer()
      		}
      		// 计算程序运行时间
      		elapsed := time.Since(t1)
      		fmt.Println("testdefer elapsed: ",elapsed)
      	}()
      }
      // test elapsed:  1.9929ms
      // testdefer elapsed:  6.0194ms
      
    • defer 的陷阱

      • defer 与 closure(闭包),如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。
      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func foo(a, b int) (i int, err error) {
          defer fmt.Printf("first defer err %v
      ", err)
          //第二个defer值拷贝。
          defer func(err error) { fmt.Printf("second defer err %v
      ", err) }(err)
          defer func() { fmt.Printf("third defer err %v
      ", err) }()
          if b == 0 {
              err = errors.New("divided by zero!")
              return
          }
      
          i = a / b
          return
      }
      
      func main() {
          foo(2, 0)
      }
      // third defer err divided by zero!
      // second defer err <nil>
      // first defer err <nil>
      
    • defer和return

      func foo() (i int) {
      	i = 0
      	defer func() {
      		fmt.Println(i)
      	}()
      	return 2
      }
      
      func main() {
          foo()//2
      }
      

      在有具名返回值的函数中(这里具名返回值为i),执行return 2的时候实际上已经将i的值重新赋值为2,所以 defer closure输出结构为2,而不是1

    • defer nil 函数

      package main
      import "fmt"
      
      //Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
      func test() {
          // 函数指向空地址
          var run func() = nil
          defer fun()
          fmt.Println("runs...")
      }
      func main() {
          defer func() {
              // recover捕捉panic输入值
              if err:= recover()l err != nil {
                  fmt.Println(err)
              }
          }()
          test()
      }
      // runs
      // runtime error: invalid memory address or nil pointer dereference
      

      解释:名为 test 的函数一直运行至结束,然后 defer 函数会被执行且会因为值为 nil 而产生 panic 异常。然而值得注意的是,run() 的声明是没有问题,因为在test函数运行完成后它才会被调用。

    • 错误的位置使用defer

      package main
      
      import (
      	"net/http"
      	"fmt"
      )
      
      
      func do() error{
      	//当 http.Get 失败时会抛出异常。
      	res,err := http.Get("http://www.google.com")
      	defer res.Body.Close()
      	if err != nil {
      		return err
      	}
      	return nil
      }
      
      func main() {
      	do()
      }
      

      因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常

    • 改进:

      • 总是在一次成功的资源分配下面使用 defer ,对于这种情况来说意味着:当且仅当 http.Get 成功执行时才使用 defer
      package main
      
      import (
      	"net/http"
      	"fmt"
      )
      
      
      func do() error{
      	//当 http.Get 失败时会抛出异常。
      	res,err := http.Get("http://www.baidudddsfw45fgsdfsfds.com")
      	// <nil> Get http://www.baidudddsfw45fgsdfsfds.com: dial tcp: lookup www.baidudddsfw45fgsdfsfds.com: no such host
      	fmt.Println(res,err)
      	if res != nil {
      		defer res.Body.Close()
      	}
      	if err != nil {
      		return err
      	}
      	return nil
      }
      
      func main() {
      	do()
      }
      

      解释:在这里,你同样需要检查 res 的值是否为 nil ,这是 http.Get 中的一个警告。通常情况下,出错的时候,返回的内容应为空并且错误会被返回,可当你获得的是一个重定向 error 时, res 的值并不会为 nil ,但其又会将错误返回。上面的代码保证了无论如何 Body 都会被关闭,如果你没有打算使用其中的数据,那么你还需要丢弃已经接收的数据。

    • defer 文件操作:

      package main
      
      import "os"
      
      
      func do() (err error) {
      	f,err := os.Open("book.txt")
      	println(f,err)
      	if err != nil {
      		println(err)
      		return err
      	}
      
      	if f != nil {
      		println("f-->",f)
      		defer func() {
      			if ferr:= f.Close();ferr != nil {
      				err = ferr
      			}
      		}()
      	}
      
      	// code...
      	return nil
      }
      
      func main() {
      	do()
      }
      

      f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉,通过命名的返回变量来返回 defer 内的错误。

    • 释放相同的资源

      package main
      
      import (
          "fmt"
          "io"
          "os"
      )
      
      func do() error {
          f, err := os.Open("book.txt")
          if err != nil {
              return err
          }
          if f != nil {
              defer func(f io.Closer) {
                  if err := f.Close(); err != nil {
                      fmt.Printf("defer close book.txt err %v
      ", err)
                  }
              }(f)
          }
      
          // ..code...
      
          f, err = os.Open("another-book.txt")
          if err != nil {
              return err
          }
          if f != nil {
              defer func(f io.Closer) {
                  if err := f.Close(); err != nil {
                      fmt.Printf("defer close another-book.txt err %v
      ", err)
                  }
              }(f)
          }
      
          return nil
      }
      
      func main() {
          do()
      }
      

      如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。当延迟函数执行时,只有最后一个变量会被用到,因此,f 变量 会成为最后那个资源 (another-book.txt)。而且两个 defer 都会将这个资源作为最后的资源来关闭

    7.异常处理

    • Golang没有结构化异常,使用panic抛出错误,recover捕获错误。异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后再defer通过recover捕获异常,然后正常处理。

    • panic

      1、内置函数
      2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
      3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
      4、直到goroutine整个退出,并报告错误
      
    • recover:

      1.内置函数
      2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
      3、一般的调用建议
          a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
          b). 可以获取通过panic传递的error
      
      
    • 注意

      1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
      2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
      3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
      
    • demo

      package main
      
      func main() {
      	test()
      }
      
      func test() {
      	defer func() {
      		if err:=recover();err != nil {
      			// fmt.Printf("%T
      ",err)
      			println(err.(string))// 将 interface{} 转型为具体类型。
      		}
      	}()
      
      	panic("panic error!")
      }
      // panic error!
      
    • panic,recover 参数类型为interface{} ,因此可抛出任何类型对象。

      func panic(v interface{})
      func recover() interface{}
      
    • 向已经关闭的通道发送数据会引发panic

      func main() {
      	defer func() {
      		// recover捕获异常
      		if err := recover(); err != nil {
      			// 打印异常
      			fmt.Println(err)//send on closed channel
      		}
      	}()
      	var ch chan int = make(chan int,10)
      	// 关闭channel
      	close(ch)
      	// 向channel 传值
      	ch <- 1
      }
      
    • 延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

      func test() {
          // 捕获最后一个错误。
      	defer func() {
      		fmt.Println(recover())//defer panic
      	}()
      	defer func() {
      		panic("defer panic")
      	}()
      	panic("test panic")
      }
      
    • 捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。

      func test() {
      	defer func() {
      		fmt.Println("-->",recover()) //无效
      	}()
      	defer recover()              //无效!
      	defer fmt.Println(recover()) //无效!
      	defer func() {
      			func() {
      					println("defer inner")
      					recover() //无效!
      			}()
      	}()
      	defer func() {
      		fmt.Println("---->",recover()) //有效
      	}()
      	panic("test panic")
      }
      // ----> test panic
      // defer inner
      // <nil>
      // --> <nil>
      
    • 使用延迟匿名函数或下面这样都是有效的。

      package main
      
      import (
          "fmt"
      )
      
      func except() {
          fmt.Println(recover())//test panic
      }
      
      func test() {
          defer except()
          panic("test panic")
      }
      
      func main() {
          test()
      }
      
    • 如果需要保护代码 段,可将代码块重构成匿名函数,如此可确保后续代码被执

      package main
      
      import "fmt"
      
      
      func test(x,y int) {
      	var z int
      	func() {
              // 捕获异常
      		defer func() {
      			if recover() != nil{
      				z = 0
      			}
      		}()
              // 触发异常
      		panic("test panic")
      		z = x/y
      		return
      	}()
      	fmt.Printf("x / y = %d
      ", z)//x / y = 0
      }
      
      func main() {
      	test(1,2)
      }
      
    • 除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。

      type error interface {
          Error() string
      }
      
    • 标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

      package main
      
      import (
      	"fmt"
      	"errors"
      )
      // 定义异常对象
      var ErrDivZero = errors.New("division by zero")
      
      
      func div(x,y int) (int, error) {
      	if y == 0{
      		return 0,ErrDivZero
      	}
      	return x/y,nil
      }
      
      func main() {
      	defer func(){
      		fmt.Println(recover())//division by zero
      	}()
      	switch z,err := div(10,0);err {
      	case nil:
      		println(z)
      	case ErrDivZero:
      		panic(err)
      	}
      }
      
      
    • Go实现try catch的异常

      package main
      
      
      import "fmt"
      
      func Try(fun func(),handler func(interface{})) {
      	defer func() {
      		// 执行defer,通过recover捕获异常赋值给err.
      		// 执行第二个参数的函数,将err传递。
      		if err:= recover();err!=nil{
      			handler(err)
      		}
      	}()
      	// 执行第一个参数的函数,触发异常
      	fun()
      }
      
      func main() {
      	// 传入匿名函数:
      	// 1.函数定义触发错误
      	// 2.函数用于打印异常
      	Try(func() {
      		panic("test panic")
      	},func(err interface{}){
      		fmt.Println(err)// test panic
      	})
      }
      
      

      如何区别使用 panic 和 error 两种方式?

      惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。

  • 相关阅读:
    Luogu4655 [CEOI2017]Building Bridges
    bzoj4892 [TJOI2017]DNA
    Luogu5058 [ZJOI2004]嗅探器
    bzoj4373 算术天才⑨与等差数列
    bzoj3122 [SDOI2013]随机数生成器
    CF940F Machine Learning
    bzoj1935 [SHOI2007]Tree 园丁的烦恼
    CF1012B Chemical table
    CF1012A Photo of The Sky
    bzoj4850 [JSOI2016]灯塔
  • 原文地址:https://www.cnblogs.com/xujunkai/p/12878519.html
Copyright © 2020-2023  润新知