• golang 读书笔记 函数 方法


    函数声明

    函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

    func name(parameter-list) (result-list) {
        body
    }
    func hypot(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(3,4)) // "5"

      在Go中,一个函数可以返回多个值。我们已经在之前例子中看到,许多标准库中的函数返回2个值,一个是期望得到的返回值,另一个是函数出错时的错误信息

    匿名函数

      拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)

    // squares返回一个匿名函数。
    // 该匿名函数每次被调用时都会返回下一个数的平方。
    func squares() func() int {
        var x int
        return func() int {
            x++
            return x * x
        }
    }
    func main() {
        f := squares()
        fmt.Println(f()) // "1"
        fmt.Println(f()) // "4"
        fmt.Println(f()) // "9"
        fmt.Println(f()) // "16"
    }

      函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

      squares的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包

    package main
    
    import "fmt"
    // prereqs记录了每个课程的前置课程
    var prereqs = map[string][]string{
        "algorithms": {"data structures"},
        "calculus": {"linear algebra"},
        "compilers": {
            "data structures",
            "formal languages",
            "computer organization",
        },
        "data structures":       {"discrete math"},
        "databases":             {"data structures"},
        "discrete math":         {"intro to programming"},
        "formal languages":      {"discrete math"},
        "networks":              {"operating systems"},
        "operating systems":     {"data structures", "computer organization"},
        "programming languages": {"data structures", "computer organization"},
    }
    func main() {
       fmt.Println("Hello, World!")
         for i, course := range topoSort(prereqs) {
            fmt.Printf("%d:	%s
    ", i+1, course)
        }
    }
    
    func topoSort(m map[string][]string) []string {
        var order []string
        seen := make(map[string]bool)
        var visitAll func(items []string)
        visitAll = func(items []string) {
            for _, item := range items {
                if !seen[item] {
                    seen[item] = true
                    visitAll(m[item])
                    order = append(order, item)
                }
            }
        }
        var keys []string
        for key := range m {
            keys = append(keys, key)
        }
        //sort.Strings(keys)
        visitAll(keys)
        return order
    }

      当匿名函数需要被递归调用时,我们必须首先声明一个变量(在上面的例子中,我们首先声明了 visitAll),再将匿名函数赋值给这个变量。如果不分成两步,函数字面量无法与visitAll绑定

    visitAll := func(items []string) {
        // ...
        visitAll(m[item]) // compile error: undefined: visitAll
        // ...
    }

    捕获迭代变量

    var slice []func()
    
    func main() {
        sli := []int{1, 2, 3, 4, 5}
        for _, v := range sli {
            fmt.Println(&v)
            slice = append(slice, func(){
                fmt.Println(v * v) // 直接打印结果
            })
        }
    
        for _, val  := range slice {
            val()
        }
    }
    // 输出 25 25 25 25 25
    结果不应该是 1, 4, 9, 16, 25 吗?

      其实原因是循环变量的作用域的规则限制。在上面的程序中,v 在 for 循环引进的一个块作用域内进行声明。在循环里创建的所有函数变量共享相同的变量,就是一个可访问的存储位置,而不是固定的值

    函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值

    var slice []func()
    
    func main() {
        sli := []int{1, 2, 3, 4, 5}
        for _, v := range sli {
            temp := v // 其实很简单 引入一个临时局部变量就可以了,这样就可以将每次的值存储到该变量地址上
            fmt.Println(&temp) // 这里内存地址是不同的
            slice = append(slice, func(){
                fmt.Println(temp *  temp) // 直接打印结果
            })
        }
    
        for _, val  := range slice {
            val()
        }
    }
    // 输出 1, 4, 9, 16, 25 预期结果

     被要求首先创建一些目录,再将目录删除实现如下:

    var rmdirs []func()
    for _, d := range tempDirs() {
        dir := d // NOTE: necessary!
        os.MkdirAll(dir, 0755) // creates parent directories too
        rmdirs = append(rmdirs, func() {
            os.RemoveAll(dir)
        })
    }
    // ...do some work…
    for _, rmdir := range rmdirs {
        rmdir() // clean up
    }

     可变参数

     参数数量可变的函数称为可变参数函数。典型的例子就是fmt.Printf和类似函数。Printf首先接收一个必备的参数,之后接收任意个数的后续参数。

    package main
    
    import "fmt"
    
       func sum(vals ...int) int {
        total := 0
        for _, val := range vals {
            total += val
        }
        return total
    }
    func main() {
    
        
        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"
    }

      在可变参数函数内部,...int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。

    package main
    
    import "fmt"
    func f(...int) {}
    func g([]int) {}
    
    func main() {
    fmt.Printf("%T
    ", f) // "func(...int)"
    fmt.Printf("%T
    ", g) // "func([]int)"
    }
    package main
    
    import "fmt"
    import "os"
    func errorf(linenum int, format string, args ...interface{}) {
        fmt.Fprintf(os.Stderr, "Line %d: ", linenum)
        fmt.Fprintf(os.Stderr, format, args...)
        fmt.Fprintln(os.Stderr)
    }
    func main() {
    
    linenum, name := 12, "count"
    errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"
    }

     interface{}表示函数的最后一个参数可以接收任意类型

     Panic异常

       Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。

    当panic异常发生时,程序会中断运行,并立即执行在该goroutine 中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息

    Recover捕获异常

      通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态。如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。

      如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

    func Parse(input string) (s *Syntax, err error) {
        defer func() {
            if p := recover(); p != nil {
                err = fmt.Errorf("internal error: %v", p)
            }
        }()
        // ...parser...
    }

      deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。我们也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。

      不加区分的恢复所有的panic异常,不是可取的做法;因为在panic之后,无法保证包级变量的状态仍然和我们预期一致。比如,对数据结构的一次重要更新没有被完整完成、文件或者网络连接没有被关闭、获得的锁没有被释放。此外,如果写日志时产生的panic被不加区分的恢复,可能会导致漏洞被忽略!!!

    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    django开发之model篇-Field类型讲解
    Scrapy+Chromium+代理+selenium
    如何在 CentOS 7 上安装 Python 3
    分享几个简单的技巧让你的 vue.js 代码更优雅
    如何用纯 CSS 创作一副国际象棋
    如何用纯 CSS 创作一个文本淡入淡出的 loader 动画
    Java8中数据流的使用
    SpringBoot中使用mybatis-generator自动生产
    Git 同时与多个远程库互相同步
    记录Java中对url中的参数进行编码
  • 原文地址:https://www.cnblogs.com/codestack/p/14905161.html
Copyright © 2020-2023  润新知