zoukankan      html  css  js  c++  java
  • Go语言完整解析Go!Go!Go!(一)数据类型 之 函数

    1,闭包与匿名函数

    闭包是匿名函数与匿名函数所引用环境的组合,即 闭包=匿名函数+引用环境,具体如何理解呢?先从例子开始

    例1:
    func squares() func() int {    //squares的返回值是一个函数类型的变量,该函数类型是func() int
        var x int
        return func() int {        //在函数内部创建一个匿名函数,将这个函数作为返回值返回给上层
            x++
            return x * x
        }
    }
    
    f := squares()    //执行squares函数得到一个函数体 
    fmt.Println(f())
    // 执行squares给创建的函数,得到结果为"1"
    fmt.Println(f()) // 再次执行,得到结果"4"
    fmt.Println(f()) // "9"
    fmt.Println(f())
    // "16"

     小小结:以上的方式就是闭包技术(closures),首先需要创建匿名函数,然后该匿名函数引用了外层函数的变量x(即引用的环境)

    var funcArray []func()
    for i := 0; i < 3; i++ {                                     for i := 0; i < 3; i++ {
                                                                     j:=i      //j是在循环中定义的
        funcArray = append(funcArray, func{print(i)})                funcArray = append(funcArray, func{print(i)})     
    }                                                            } 
    funcArray[0]() //结果:3 //结果:1
    funcArray[1]() //结果:3 //结果:2
    funcArray[2]() //结果:3 //结果:3

    小小结:第一个,闭包引用的环境是外层函数的变量i,当执行完循环之后,变量i的值为3,于是再执行闭包时,得到的结果就是3
    第二个,
    闭包引用的环境是外层函数的变量j,不同的闭包得到的是不同的j(因为j是在循环中定义的)。
    注意,如果j也是在循环外定义的,然后循环中是 j=i,则最终的结果是 222,想想为什么呢? 

    小结:

    闭包有什么好处?应用场景是啥?

    a,可以通过闭包将函数内部的局部变量暴露出来

    b,闭包的记忆效应:闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应,此时的变量就像全局变量一般的存在。

    c, 让某些变量的值始终保持在内存中,只要闭包的引用还在,就不会在调用外层函数结束后,变量被被垃圾回收(garbage collection)。

    2,可变参数函数与切片

    func sum(vals ...int) int{
    total := 0
    for _, val := range vals{
       total += val
    }
    return total
    }
    sum() 
    sum(
    3)
    sum(
    1,2,3,4)

    var values []int{10,20,30}
    sum(values...)

    小小结:...类型:可以看做是一种复合类型,表示一个type类型的切片(包括底层的数组),一堆入参进来后,将这些参数复制到数组中,于是可以利用遍历的方式进行访问
    变量...: 首先变量必须是某种类型的切片(当然一定要有底层对应的数组),然后 变量...就表示多个参数,每一个参数都是数组中的值
    即, ...类型 和 变量... 两种形式,正式一对!

    2,defer的运用(闭包 + defer)

            func double(x int)(result int){
    0)方式零: defer
    fmt.Println("defer:", x) //defer的是一个语句,不是函数(闭包)
    1)方式一: defer func(){result += x}() //首先构造闭包(引用了环境x和result),闭包的引用也在函数内,在result之前将环境中的result和x相加
    2)方式二: defer func(i int){result += i}(x) //闭包只引用了环境变量result,同样闭包的引用在函数内(所以要在闭包定义后用 闭包体(入参) 的格式调用)
          x++
          return x
         }
    0)double(10)   //结果为10。在执行到defer的时候,语句入栈,此时x的值已经固定
    1)double(10) //结果为22。return之时,x的值为11,result等于x的值也是11,之后再执行闭包就变成了22
    2)double(10) //结果为21. 在执行到defer语句的时候,闭包加载,此时入参i的值已经确定,即外层的x(10),return之时,i为10,result是11,执行闭包的结果就是21
    小小结:return不是一个原子指令,包括 给返回值值赋值 和 真正ret 2步,defer的内容(语句/闭包)的执行被放在二者之间
    被defer的函数称为defered函数,整个的执行原理是:
    1)根据defer关键字,将defered函数入栈,如果有多个defer语句的话,则递归依次入栈
    2)等到执行到return语句时,defered函数依次出栈,执行
    3)关于入参的值,是在入栈的时候已经确定
    4)如果闭包中引入了外部变量,则原理同一般的闭包
        被defer的语句同理,只是如果引用了某个变量,则入栈的值就是当前值

    2,panic和recover

    一班而言,当程序panic异常了,程序会中断,并立刻执行该gouroutine(main所在的也是一个goroutine)中被defered的内容,即依次出栈

                       随后,程序崩溃系统给出日志信息,包括panic value和堆栈跟踪信息;

    有了recover,他会让recover的函数不继续,但正常返回,这样但是整个goroutine还在

    func f(x int){
        defer func(){
            if recover() != nil{fmt.Println("before exec defer:recover after panic")}
        }()    
        fmt.Printf("f(%d)
    ", x + 0/x)
        f(x-1)
    }
    
    func PrintStack()  {
        var buf [4096]byte
        n := runtime.Stack(buf[:],false)   //为什么一定要buf[:]
        os.Stdout.Write(buf[:n])
    }
    
    执行:
    defer PrintStack()  
    f(1)   

    结果:
    f(1)

    goroutine 1 [running]:    ##这一坨的错误信息是PrintStack()函数打印的信息=堆栈信息
    main.PrintStack()         ##根据当前的这个打印可以知道,当panic的时候,处于栈顶的是之前defered函数即PrintStack函数,之后才是panic函数,
      C:/Users/HX/go/src/myWork/testFunction.go:54 +0x62 
    panic(0x4a6ba0, 0x533490)   ##我就是panic函数,panic函数下面正是即将引起panic的语句
      C:/go1.10/src/runtime/panic.go:505 +0x237
    main.f(0x0)
      C:/Users/HX/go/src/myWork/testFunction.go:48 +0xde      ##是的,我就是真正引起panic的的语句
    main.f(0x1)
      C:/Users/HX/go/src/myWork/testFunction.go:49 +0xcf
    main.testFunction()
      C:/Users/HX/go/src/myWork/testFunction.go:60 +0x4d
    main.main()
      C:/Users/HX/go/src/myWork/main.go:4 +0x29

    ------------------------------------------------------------
    panic: runtime error: integer divide by zero    #这一坨为系统直接报出的错误信息=错误原因(panic value) + 堆栈信息

    goroutine 1 [running]:
    main.f(0x0)
      C:/Users/go/src/myWork/testFunction.go:48 +0xde   #这一行正是执行0/0的那一杨,所以出错了,于是把目前还在栈中的所有内容打印出来,这里就是栈顶
    main.f(0x1)
      C:/Users/go/src/myWork/testFunction.go:49 +0xcf
    main.testFunction()
      C:/Users/go/src/myWork/testFunction.go:60 +0x4d
    main.main()
      C:/Users/go/src/myWork/main.go:4 +0x29

    Process finished with exit code 2

    ======如果有recover操作,则只会由PrintStack()函数打印堆栈信息,panic不会===================================================================

    f(1)
    before exec defer:recover after panic   #执行了recover操作
    goroutine 1 [running]:
    main.PrintStack()
    C:/Users/go/src/myWork/testFunction.go:54 +0x62    #这里是打印本身的语句,即PrintStack的打印语句
    main.testFunction()
    C:/Users/go/src/myWork/testFunction.go:75 +0x4e
    main.main()
    C:/Users/go/src/myWork/main.go:4 +0x29
    hello go, I also remember you!hahah!        #这里显示程序正常执行下去了,已经到结尾
    Process finished with exit code 0

    小结:

    //runtime/runtime2.go
    type _panic struct {
        // 调用defer时入参的指针
        argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
        arg interface{} // argument to panic
        link *_panic // link to earlier panic
        recovered bool // whether this panic is over
        aborted bool // the panic was aborted
    }
    //runtime/panic.go
    func gopanic(e interface{}) {
        gp := getg()
        ...
        // 初始化panic
        var p _panic
        ...
    
        // 遍历 G 的defer链表
        for {
            d := gp._defer
            ...//调用defer后面的函数。如果函数中包含了recover,那么会调用gorecover
            reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
           ...// 已经有recover被调用
            if p.recovered {
                mcall(recovery)
                throw("recovery failed") // mcall should not return
            }
        }
    
        // 终止程序
        fatalpanic(gp._panic) // should not return
        *(*int)(nil) = 0      // not reached
    }

    1,首先要明确,go有运行时即runtime在帮我们运行程序,于是程序执行异常的时候,运行时会替我们调用panic函数,对应到运行时层面的函数就是gopanic

               当然,我们可以在用户程序层面直接调用panic,效果一样的

    2,然后可以看到,panic函数执行时,会去遍历G的defer列表,于是在异常退出之前先去执行defer函数,这也就是为什么PrintStack函数得以执行,同时此时此刻的堆栈中有defered  函数的信息

    3,recover的加入,就是利用了defer的原理,即在退出之前会去执行以下recover,于是recover就得以处理以下堆栈,让当前goroutine不会异常退出,具体来说就是当前的子函数正常return了

              

    
    
  • 相关阅读:
    Linux标准C头文件和内核头文件的理解
    linux GCC编译C程序的问题
    C#类型反射、晚期绑定、特性编程的使用背景与分析
    linux下C#开发mongoDB
    C中“指针和数组”引发的探索一
    C#学习记录
    linux下Apache+PHP+mysql+phpMyAdmin源码包安装配置
    基于mongoDB和C#分布式海量文件存储实验
    C中指针和数组引发的探索二
    算法研究学习一(用C和C#实现)
  • 原文地址:https://www.cnblogs.com/shuiguizi/p/11380635.html
Copyright © 2011-2022 走看看