函数
函数声明
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func name (parameter-list) (result-list) { body }
下面是几种声明示例:
func add (x int , y int ) int { return x + y }func sub (x, y int ) (z int ) { z = x -y; return }func first (x int , _ int ) int { return x }func zero (int , int ) int { return 0 }
函数声明上的参数称作形参,传入函数的参数称作实参。
形参:都是函数的局部变量,初始值由调用者提供的实参传递
实参:按值传递,函数接受到的是实参的副本,所以修改函数的形参并不会影响到实参。但是如果提供的实参包含引用类型,比如指针、slice、map、函数或者通道,那么函数使用形参变量时就有可能会间接的修改实参。
递归
许多编程语言使用固定长度大小的函数调用栈,大小在64kb到2mb之间。递归的深度会受限于固定长度的栈大小,所以当进行深度递归调用的时候必须谨防栈溢出 。GO语言实现了可变长度的栈,栈的大小会随着使用而增长,可达到1GB左右的上限。
多返回值
习惯上最后一个布尔返回值表示成功与否
一个函数如果有命名的返回值,可以省略return语句的操作数,这称作裸返回
例如:
func CountWordsAndImages (url string ) (words, images int , err error ) { resp, err := http.Get(url) if err != nil { return } doc, err := http.Parse(resp.Body) resp.Body.Close() if err != nil { return } words, images = countWordsAndImages(doc) return }
错误
错误处理策略
传递下去
超出一定重试次数和限定的时间后报错退出
停止程序
只记录错误信息,程序继续运行
罕见的情况下我们可以直接安全的忽略掉整个日志
dir, err := ioutil.TemDir("" , "scrath" ) if err!=nil { return fmt.Errorf("fail" ) } os.RemoveAll(dir)
文件结束标识
io包保证任何由文件结束引起的读取错误,始终都会得到一个与众不同的错误--io.EOF,它的定义如下:
package ioimport "errors" var EOF = errors.New("EOF" )
函数值(函数变量)
函数值就像其他值,函数变量也有类型,可以赋给变量或者传递或者从其他函数中返回。函数变量可以像其他函数一样调用。比如:
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 )) f = negative fmt.Println(f(3 )) fmt.Printf("%T\n" , f) f = product
匿名函数
命名函数只能在包级别的作用域进行声明,但我们能够使用函数字面量在任何表达式内指定函数变量。函数字面量就像函数声明,但在func关键字后面没有函数的名称。它是一个表达式,它的值称作匿名函数。
import "strings" strings.Map(func (r rune ) rune {return r+1 }, "HAL-9000" )
警告:捕获迭代变量
var rmdirs []func () for _, d := range tempDirs() { dir := d os.MkdirAll(dir, 0755 ) rmdirs = append (rmdirs, func () { os.RemoveAll(dir) }) } for _, rmdir := range rmdirs { rmdir() }
这里你可能会奇怪,为什么循环体内要将循环变量赋值给一个新的余部变量dir,而不是直接append循环变量d。
这是错误示范:
var rmdirs []func () for _, dir := range tempDirs() { os.MkdirAll(dir, 0755 ) rmdirs = append (rmdirs, func () { os.RemoveAll(dir) }) }
这个原因是循环变量的作用域的规则限制。dir 在for循环引进的一个块作用域内进行声明。在循环里创建的所有函数变量共享相同得到变量——一个可访问的存储位置,而不是固定的值。dir 变量的值在不断的迭代中更新,因此,当最后调用清理函数的时候,dir 变量已经被每一次的for循环更新多次。因此,dir 变量的实际取值是最后一次迭代时的值。
可变参数(变长函数)
变长函数被调用的时候可以有可变的参数个数。在参数列表最后类型名称之前使用省略号“...”表示声明一个变长函数,调用这个函数的时候可以传递该类型任意数目的参数。
例如:
func sum (vals ...int ) int { total := 0 for _, val := range vals { total += val } return total } sum() sum(3 ) sum(1 ,2 ,3 ,4 ) values := []int {1 ,2 ,3 ,4 } sum(values...)
Deferred函数
使用defer语句后,无论是正常执行完毕,或者宕机异常等,实际的调用都推迟到包含defer语句的函数结束后才执行。
defer语句没有限制使用次数,执行的时候按照调用defer语句顺序的倒叙执行。
func double (x int ) (result int ) { defer func () { fmt.Printf("double(%d) = %d\n" , x, result) } return x + x } _ = double(4 )
Panic异常
package mainimport ( "fmt" "runtime" "os" ) func f (x int ) { fmt.Printf("f(%d)\n" , x+0 /x) defer fmt.Printf("defer %d\n" , x) f(x - 1 ) } func printStack () { var buf [4096 ]byte n := runtime.Stack(buf[:], false ) os.Stdout.Write(buf[:n]) } func main () { defer printStack() f(3 ) }
输出:
f(3) f(2) f(1) defer 1 defer 2 defer 3 goroutine 1 [running]: E:/study/go_from_zero/ch5/defer2.go:24 +0x65 panic(0x469560, 0x513c60) E:/scriptpath/go/src/runtime/panic.go:965 +0x1c7 main.f(0x0) E:/study/go_from_zero/ch5/defer2.go:11 +0x1e8 main.f(0x1) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.f(0x2) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.f(0x3) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.main() E:/study/go_from_zero/ch5/defer2.go:18 +0x53 panic: runtime error: integer divide by zero goroutine 1 [running]: main.f(0x0) E:/study/go_from_zero/ch5/defer2.go:11 +0x1e8 main.f(0x1) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.f(0x2) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.f(0x3) E:/study/go_from_zero/ch5/defer2.go:13 +0x189 main.main() E:/study/go_from_zero/ch5/defer2.go:18 +0x53 exit status 2
Recover捕获异常
退出程序通常是正确处理宕机的方式,但也有例外。在一定情况下是可以进行回复的,至少有时候可以在退出前理清当前混乱的情况。
func soleTitle (doc *html.Node) (title string , err error ) { type bailout struct {} defer func () { switch p:= revcover(); p { case nil : case bailout{}: err = fmt.Errof("multiple title elements" ) default : panic (p) } }() }
练习
练习 5.1 :
修改findlinks代码中遍历n.FirstChild链表的部分,将循环调用visit,改成递归调用。
练习 5.2 :
编写函数,记录在HTML树中出现的同名元素的次数。
练习 5.3 :
编写函数输出所有text结点的内容。注意不要访问 <script> 和
<style> 元素,因为这些元素对浏览者是不可见的。
练习 5.4 :
扩展visit函数,使其能够处理其他类型的结点,如images、scripts和style
sheets