Go语言学习笔记 - 第五章 函数(The Go Programming Language)
2020-01-13 06:07
471 查看
第五章 函数
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-nil
,error
的Error
可以打印错误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的函数执行完毕执行- 无论
return
或panic
,defer
都会执行 - 一个函数中可以有多个
defer
,执行顺序和声明顺序相反
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁
defer机制也常被用于记录何时进入和退出函数
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)) } }
- 因为
defer
在return
后执行,所以可以通过匿名函数拿到包括返回值在内的所有变量的值。 - 被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值
- 在循环体中的
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
时调用recover
,recover
会返回nil
- 对
panic
异常的恢复处理要慎重,panic
后的恢复无法保证包级变量不出问题。。比如,对数据结构的一次重要更新没有被完整完成、文件或者网络连接没有被关闭、获得的锁没有被释放。此外,如果写日志时产生的panic被不加区分的恢复,可能会导致漏洞被忽略。 - 不该试图去恢复其他包引起的panic;
- 公有的API应该将函数的运行失败作为error返回,而不是panic
- 不应该恢复一个由他人开发的函数引起的panic,比如说调用者传入的回调函数,因为你无法确保这样做是安全的
- 有时我们很难完全遵循规范,举个例子,net/http包中提供了一个web服务器,不能因为某个用户的处理Handler函数引发的panic异常,杀掉整个进程;web服务器遇到处理函数导致的panic时会调用recover,输出堆栈信息,继续运行。
- 有些情况下,我们无法恢复。某些致命错误会导致Go在运行时终止程序,如内存不足。
常用库及方法
recover()
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- Go语言学习笔记 - 第十二、十三章 反射、底层编程系统(The Go Programming Language)
- Go语言学习笔记 - 第十一章 单元测试(The Go Programming Language)
- Go语言学习笔记 - 第十章 包机制和包的组织结构(The Go Programming Language)
- Go语言学习笔记 - 第九章 基于共享变量并发编程(传统)(The Go Programming Language)
- Go语言学习笔记 - 第七章 接口(The Go Programming Language)
- Go语言学习笔记 - 第六章 方法(The Go Programming Language)
- The C++ Programming Language 学习笔记 第7章 函数
- The C++ Programming Language 学习笔记 第四章 类型和声明
- the-swift-programming-language 学习笔记
- The C++ Programming Language Special 3rd Edition学习笔记-[1]序言
- The C programming language 学习笔记(一)
- The C programming language 学习笔记
- The C++ Programming Language Special 3rd Edition学习笔记-[3]第二章 C++概览
- The C++ Programming Language Special 3rd Edition学习笔记-[2]第一章 致读者
- The C Programming Language 学习笔记
- the c programming language second edition 第四章函数与程序结构笔记及练习题中
- the c programming language second edition 第四章函数与程序结构笔记及练习题上
- The C++ Programming Language 学习笔记(ch1 ,ch2)
- The C++ Programming Language 学习笔记 第5章 指针、数组和结构
- The C++ Programming Language 学习笔记 第6章 表达式和语句