go语言圣经第五章(读书笔记)

第五章 函数

函数声明

  • 没有默认参数值
  • 实参通过值传递,形参是实参的拷贝
  • 实参是引用类型(指针,slice,map,function,channel等)时,实参可能会被修改
  • 可能会遇到没有函数体的函数声明,这表示该函数不是Go实现的,例如
package math
func Sin(x float64) float //implemented in assembly language

递归

  • 大部分编程语言使用固定大小的函数调用栈,常见打下从64KB到2MB不等。但是,Go采用可变栈,这使得我们使用递归时不必考虑溢出和安全问题

多返回值

错误

  • 在Go的错误处理中,程序运行失败时会返回错误信息,是预期的结果而非异常
  • 当错误原因只有一个,用一个布尔值,通常被命名为ok就可以了
  • 当错误原因有很多个,用户需要了解更多错误信息,会用到error类型
  • 内置的error是接口类型,它的值为nil或non-nil

错误处理策略

  • 传播错误:函数中某个子程序失败,会变成该函数的失败
    1.可以使用fmt.Errorf格式化错误信息
    2.错误信息经常以链式组合在一起,故错误信息中应避免大写和换行符
    3.编写错误信息时,要确保错误信息对问题细节的描述是详尽的
  • 偶然性错误:如果错误发生是偶然性的,或不可预知的问题导致的
    1.重新尝试失败操作(限制重试的时间间隔或重试的次数,防止无限制的重试)
  • 无法运行错误:如果错误发生后,程序无法继续运行
    1.输出错误信息并结束程序(这种策略只在main中执行)
    2.对库函数而言,应仅向上传播错误
    3.如果错误意味着程序内部包含不一致性,即遇到了bug,才能在库函数中结束程序
// (In function main.)
if err := WaitForServer(url); err != nil {
    fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
    os.Exit(1)
}

4.或者更简洁的写法(与上例等价)

if err := WaitForServer(url); err != nil {
    log.Fatalf("Site is down: %v\n", err)
}
  • 无影响的错误:只需要输出错误信息就够了,不需要中断程序的运行
//log包中的所有函数会为没有换行符的字符串增加换行符
if err := Ping(); err != nil {
    log.Printf("ping failed: %v; networking disabled",err)
}
if err := Ping(); err != nil {
    fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabled\n", err)
}
  • 可忽略错误:我们可以直接忽略掉错误
dir, err := ioutil.TempDir("", "scratch")
if err != nil {
    return fmt.Errorf("failed to create temp dir: %v",err)
}
// ...use temp dir...
os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically
  • 错误处理推荐风格:
    1.检查某个子函数是否失败后,我们通常将处理失败的逻辑代码放在处理成功的代码之前。
    2.如果某个错误会导致函数返回,那么成功时的逻辑代码不应放在else语句块中,而应直接放在函数体中
    3.换句话说,先进行一系列的初始检查,防止错误发生,之后是函数的实际逻辑

文件结尾错误(EOF)

  • io包保证任何由文件结束引起的读取失败都返回同一个错误:io.EOF
package io
import "errors"
// EOF is the error returned by Read when no more input is available.
var EOF = errors.New("EOF")

函数值

  • 在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其它变量,传递给函数,从函数返回
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

var f func(int) int
f(3) // 此处f的值为nil, 会引起panic错误
//还可以和nil比较,但函数值之间不可比较
if f != nil {
    f(3)
}

匿名函数(anonymous function)

strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
// 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()可以访问和更新x,这意味着匿名函数和squares中,存在变量引用(函数值属于引用类型和函数不可比较的原因)
  • go使用闭包(closures)技术实现函数值,通常也把函数值称为闭包
  • 当匿名函数需要递归时,需要声明一个变量
func main() {
    for i, course := range topoSort(prereqs) {
        fmt.Printf("%d:\t%s\n", 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
}

警告:捕获迭代变量

  • 可行的做法:
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
}
  • 不可行的方案:
var rmdirs []func()
for _, dir := range tempDirs() {
    os.MkdirAll(dir, 0755)
    rmdirs = append(rmdirs, func() {
        os.RemoveAll(dir) // NOTE: incorrect!
    })
}
  • 问题的原因在于循环变量的作用域。
    1.for循环引入了新的词法块,循环变量dir在这个词法块中被声明
    2.在该循环中生成的所有函数值都共享相同的循环变量
    3.函数值记录的是循环变量的内存地址,而不是循环变量某一时刻的值

可变参数

  • 声明可变参数时需要加上...
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"
}
  • 显然,上面例子的vals..int被当作[]int处理了
  • sum(1,2,3,4)隐式创建了一个数组,将数组作为参数切片,传给了sum

Deferred函数

  • 使用defer关键字,跟在其后的函数将被延迟执行:
    1.直到包含该defer语句的函数执行完毕,defer语句后的函数才被执行,
    2.无论再此之前是否执行了return或者panic
    3.可以在一个函数中执行多条defer语句,执行顺序和他们的声明顺序相反
    4.在循环体中的defer语句,只有在函数执行完毕后,延迟的函数才被执行
for _, filename := range filenames {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close() // NOTE: risky; could run out of file
    descriptors
    // ...process f...
}

5.解决的方法是将defer移动到另外的函数

for _, filename := range filenames {
    if err := doFile(filename); err != nil {
        return err
    }
}
func doFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    // ...process f...
}

Panic异常

  • 运行时检查,引起的错误就是panic异常
  • 一般而言,panic发生后程序会中断运行,并立即执行该goroutine中的defer,之后,程序崩溃并输出日志信息
  • 日志信息包括panic value和函数调用的堆栈跟踪信息
  • 可以人为引发panic(只在非常严重的错误下使用):使用内置的panic函数
  • runtime包允许程序员输出堆栈信息
gopl.io/ch5/defer2
func main() {
    defer printStack()
    f(3)
}
func printStack() {
    var buf [4096]byte
    n := runtime.Stack(buf[:], false)
    os.Stdout.Write(buf[:n])
}

Recover捕获异常

  • “举个例子,当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...
}

猜你喜欢

转载自www.cnblogs.com/laiyuanjing/p/11229141.html