Go语言学习笔记 - 第五章 函数(The Go Programming Language)

第五章 函数

5.1函数声明

划重点

  • 函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func name(parameter-list) (result-list) {
body
}
  • 形参为局部变量
  • 没有返回值或者一个无名变量时,返回的括号可以省略。
  • 返回值也可以像形式参数一样被命名,并被声明成一个局部变量。
  • 如果形参或返回值有相同的类型,参数类型可以省略
func f(i, j, k int, s, t string) { /* ... */ }
func f(i int, j int, k int, s string, t string) { /* ... */ }
  • 函数的类型被称为函数的标识符
  • Go语言没有默认参数值
  • 函数的形参和有名返回值作为函数最外层的局部变量,被存储在相同的词法块中。
  • 实参通过值的方式传递,因此函数的形参是实参的拷贝
  • 对形参进行修改不会影响实参。引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会被修改
  • 没有函数体的函数声明,这表示该函数不是以Go实现的
package math
func Sin(x float64) float //implemented in assembly language

5.2递归

划重点

  • golang.org/x/… 目录下存储了一些由Go团队设计、维护,对网络编程、国际化文件处理、移动平台、图像处理、加密解密、开发者工具提供支持的扩展包
  • 大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等,递归深度会导致栈溢出。
  • Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。

常用库及方法

  • golang.org/x/net/html
  • html.Parse html.ElementNode html.Node.FirstChild html.Node.NextSibling html.NewTokenizer

5.3多返回值

划重点

  • 在Go中,一个函数可以返回多个值
  • 许多标准库中的函数返回2个值,一个是期望得到的返回值,另一个是函数出错时的错误信息
  • 通过fmt.Errorf(§7.8)输出详细的错误信息
  • resp.Body需要确保被关闭,释放网络资源。虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此我们必须显式的释放这些资源。
  • 函数调用者必须显式的将返回值分配给变量
  • 对于可以接受多参数的函数,可将多返回值的函数作为该函数的参数
  • bare return返回值除了类型还带有变量名,那么该函数的return语句可以省略操作数。

常用库及方法

5.4错误

划重点

  • panic是来自被调函数的信号,表示发生了某个已知的bug。一个良好的程序永远不应该发生panic异常。
  • 通过最后一个返回值,来传递错误信息。如果错误原因只有一个,可以设置为布尔值,这时可命名为ok
  • 内置的error是接口类型
  • error类型可能是nil或者non-nilerrorError可以打印错误string
  • Go有别于那些将函数运行失败看作是异常(exception)的语言,这些语言会将错误当做异常处理,并输出堆栈信息,无法帮助定位错误。
  • Go使用控制流机制(如if和return)处理异常

常用库及方法

  • strings.Contains
  • strconv.FormatBool
  • time.Date time.Time
  • cache.Lookup
  • error.Error

5.4.1错误处理策略

划重点

  • 常用的5中处理错误的方式
    • 传播错误,调用者直接返回被调用函数的返回值
    • 偶发性或者不可预知的错误,使用重新尝试失败的操作
    • 输出错误信息并结束程序,os.Exit(),注意该策略只应在main中执行
    • 输出错误信息,不中断程序,使用log包或fmt.Fprintf(os.Stderr...
    • 直接忽略掉错误
  • fmt.Errorf函数使用fmt.Sprintf格式化错误信息并返回
  • log包中的所有函数会为没有换行符的字符串增加换行符

常用库及方法

  • fmt.Errorf
  • os.Exit() os.RemoveAll
  • log.Fatalf log.SetPrefix() log.SetFlags()
  • ioutil.TempDir

5.4.2文件结尾错误(EOF)

划重点

  • io包中任何由文件结束引起的读取失败都返回同一个错误——io.EOF

5.5函数值

划重点

  • 函数可以看做是一种值,第一类值(first-class values)
  • 函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回
  • 函数类型的零值是nil。调用值为nil的函数值会引起panic错误
  • 函数值可以与nil比较
  • 函数值之间是不可比较的,也不能用函数值作为map的key

常用库及方法

  • strings.Map

5.6 匿名函数

划重点

  • 匿名函数存在变量引用,这是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。
  • 当匿名函数需要被递归调用时,必须首先声明一个变量,再将匿名函数赋值给这个变量,必须分两步,无法直接绑定,无法递归调用。
visitAll := func(items []string) {
// ...
visitAll(m[item]) // compile error: undefined: visitAll
// ...
}
  • append的参数“f(item)...”,会将f返回的一组元素一个个添加到
    worklist中。worklist = append(worklist, f(item)...)

常用库及方法

  • sort.Strings
  • http.StatusOK
  • resp, err := http.Get(url) resp.Request.URL.Parse

5.6.1 警告:捕获迭代变量

划重点

  • 这是,Go词法作用域的一个陷阱。
  • 下面的例子是错误的,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值,for循环已完成,dir中存储的值等于最后一次迭代的值。
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect!
})
}

解决这个问题可以:

for _, dir := range tempDirs() {
dir := dir // declares inner dir, initialized to outer dir
// ...
}
  • 如果你使用go语句或者defer语句会经常遇到此类问题

5.7可变参数

划重点

  • 参数数量可变的函数称为为可变参数函数,典型的是fmt.Printf
  • 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示该函数会接收任意数量的该类型参数。
  • 调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调函数
  • 如果原始参数已经是切片类型,只需在最后一个参数后加上省略符...,下面的调用是一致的:
fmt.Println(sum(1, 2, 3, 4)) // "10"
//----
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
  • 可变参数函数和以切片作为参数的函数是不同的,虽然看起来类似
func f(...int) {}
func g([]int) {}
fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"

5.8Deferred函数

划重点

  • resp.Body.close关闭网络连接
  • defer用于延迟执行操作,具有以下特性:
    • defer后面的函数会被延迟执行
    • defer后的函数会在包含该defer的函数执行完毕执行
    • 无论returnpanicdefer都会执行
    • 一个函数中可以有多个defer,执行顺序和声明顺序相反
  • defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁
  • 调试复杂程序时,defer机制也常被用于记录何时进入和退出函数
  • 一个有意思的用法defer trace(“bigSlowOperation”)()
func bigSlowOperation() {
	defer trace("bigSlowOperation")() // don't forget the extra >parentheses
	// ...lots of work…
	time.Sleep(10 * time.Second) // simulate slow operation by >sleeping
}


func trace(msg string) func() {
	start := time.Now()
	log.Printf("enter %s", msg)
	return func() {
		log.Printf("exit %s (%s)", msg,time.Since(start))
	}
}
  • 因为deferreturn后执行,所以可以通过匿名函数拿到包括返回值在内的所有变量的值。
  • 被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值
  • 在循环体中的defer语句需要注意,他们不会在循环体中针对某一次循环执行,而是在整个函数执行完毕后,才会执行。比较有效的做法是把其中的逻辑独立出来形成一个函数。
  • 在关闭文件时,没有对f.close采用defer机制,因为这会产生一些微妙的错误。许多文件系统,尤其是NFS,写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息,可能会导致数据丢失,而我们还误以为写入操作成功。
    如果要使用defer f.close(),可以使用上面的通过defer 匿名函数的方式,在defer里面改写返回的error信息
    defer func() {
    // Close file, but prefer error from Copy, if any.
    if closeErr := f.Close(); err == nil {
    	err = closeErr
    }
    }()
    

常用库及方法

  • resp.Header.Get("Content-Type") resp.Body.close
  • strings.HasPrefix
  • sync.Mutex sync.Mutex.Lock sync.Mutex.Unlock
  • resp.Request.URL.Path
  • path.Base
  • os.Create f.Close()
  • io.Copy

5.9Panic异常

划重点

  • panic异常是指只能在运行时检查,如数组访问越界、空指针引用等
  • panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制),程序崩溃并输出日志信息
  • 日志信息包括panic value和函数调用的堆栈跟踪信息。panic value通常是某种错误信息。
  • panic异常除了来自运行时,也可以直接调用内置的panic函数,panic函数接受任何值作为参数
  • Go的panic机制类似于其他语言的异常,但panic的适用场景有一些不同。golang建议尽量使用Go提供的错误机制,而不是panic,尽量避免程序的崩溃。
  • 为了方便诊断问题,runtime包允许程序员输出堆栈信息
  • 在Go的panic机制中,延迟函数的调用在释放堆栈信息之前

常用库及方法

  • panic()
  • regexp.Compile regexp.MustCompile
  • template.Must
  • runtime.Stack os.Stdout.Write

5.10Recover捕获异常

划重点

  • 对panic正常不应该做处理,但有时可以从异常panic中恢复以帮助在程序崩溃前做一些操作。比如,web服务器可以在崩溃前关闭连接。
  • 如果要恢复panic,比如在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recoverrecover会返回nil
  • panic异常的恢复处理要慎重,panic后的恢复无法保证包级变量不出问题。。比如,对数据结构的一次重要更新没有被完整完成、文件或者网络连接没有被关闭、获得的锁没有被释放。此外,如果写日志时产生的panic被不加区分的恢复,可能会导致漏洞被忽略。
  • 不该试图去恢复其他包引起的panic;
  • 公有的API应该将函数的运行失败作为error返回,而不是panic
  • 不应该恢复一个由他人开发的函数引起的panic,比如说调用者传入的回调函数,因为你无法确保这样做是安全的
  • 有时我们很难完全遵循规范,举个例子,net/http包中提供了一个web服务器,不能因为某个用户的处理Handler函数引发的panic异常,杀掉整个进程;web服务器遇到处理函数导致的panic时会调用recover,输出堆栈信息,继续运行。
  • 有些情况下,我们无法恢复。某些致命错误会导致Go在运行时终止程序,如内存不足。

常用库及方法

  • recover()

猜你喜欢

转载自blog.csdn.net/rabbit0206/article/details/103758415