函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。数组
func function-name(param...) (result...) { body }
形式参数列表描述了函数的参数名以及参数类型。这些参数做为局部变量,其值由参数调用者提供。返回值列表描述了函数返回值的变量名以及类型。若是函数返回一个无名变量或者没有返回值,返回值列表的括号是能够省略的。若是一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。数据结构
func hypot(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3,4)) // "5"
函数能够是递归的,这意味着函数能够直接或间接的调用自身。对许多问题而言,递归是一种强有力的技术,例如处理递归的数据结构。函数
func a(i int) (res int){ if i == 1 { return i } return i * a(i - 1) } fmt.Println(a(5)) // 120
在Go中,一个函数能够返回多个值。google
func calculation(a,b int)(add,sub int) { add = a + b sub = a - b }
在Go中有一部分函数老是能成功的运行,对各类可能的输入都作了良好的处理,使得运行时几乎不会失败,除非遇到灾难性的、不可预料的状况,好比运行时的内存溢出。致使这种错误的缘由很复杂,难以处理,从错误中恢复的可能性也很低。指针
panic是来自被调函数的信号,表示发生了某个已知的bug。一个良好的程序永远不该该发生panic异常。日志
value, ok := cache.Lookup(key) if !ok { // ...cache[key] does not exist… }
当一次函数调用返回错误时,调用者有应该选择什么时候的方式处理错误。根据状况的不一样,有不少处理方式.code
resp,err := http.Get("https://www.google.com") if err != nil { fmt.Println(err) }
函数常常会返回多种错误,这对终端用户来讲可能会颇有趣,但对程序而言,这使得状况变得复杂。不少时候,程序必须根据错误类型,做出不一样的响应。让咱们考虑这样一个例子:从文件中读取n个字节。若是n等于文件的长度,读取过程的任何错误都表示失败。若是n小于文件的长度,调用者会重复的读取固定大小的数据直到文件结束。这会致使调用者必须分别处理由文件结束引发的各类错误。基于这样的缘由,io包保证任何由文件结束引发的读取失败都返回同一个错误——io.EOF,该错误在io包中定义:递归
package io import "errors" // EOF is the error returned by Read when no more input is available. var EOF = errors.New("EOF")
调用者只需经过简单的比较,就能够检测出这个错误。下面的例子展现了如何从标准输入中读取字符,以及判断文件结束。内存
in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break // finished reading } if err != nil { return fmt.Errorf("read failed:%v", err) } // ...use r… }
由于文件结束这种错误不须要更多的描述,因此io.EOF有固定的错误信息——“EOF”。对于其余错误,咱们可能须要在错误信息中描述错误的类型和数量,这使得咱们不能像io.EOF同样采用固定的错误信息。在7.11节中,咱们会提出更系统的方法区分某些固定的错误值。input
在Go中,函数被看做第一类值(first-class values):函数像其余值同样,拥有类型,能够被赋值给其余变量,传递给函数,从函数返回。对函数值(function value)的调用相似函数调用。
func add(a,b int) (sum int) { sum = a + b } func main() { f = add fmt.Println(sum(1,2)) }
函数类型的零值是nil。调用值为nil的函数值会引发panic错误:
var f func(int) int f(3) // 此处f的值为nil, 会引发panic错误
拥有函数名的函数只能在包级语法块中被声明,经过函数字面量(function literal),咱们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明类似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被成为匿名函数(anonymous function)。
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" }
参数数量可变的函数称为为可变参数函数。典型的例子就是fmt.Printf和相似函数。Printf首先接收一个的必备参数,以后接收任意个数的后续参数。
在声明可变参数函数时,须要在参数列表的最后一个参数类型以前加上省略符号“...”,这表示该函数会接收任意数量的该类型参数。
func main() { fmt.Println(sum(1,2,3,4,5)) // 15 var sli = []int{1,2,3,4,5} fmt.Println(sli) // [1 2 3 4 5] fmt.Println(sum(sli...)) // 15 } func sum(values ...int) int { sum := 0 for _, v := range values { sum += v } return sum }
Go的类型系统会在编译时捕获不少错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引发painc异常。
通常而言,当panic异常发生时,程序会中断运行,并执行此goroute上的defer函数。
当某些不该该发生的场景发生时,咱们就应该调用panic。
name := "zhaohaiyu" switch name { case "zhy": fmt.Println("zhy") case "haiyuzhao": fmt.Println("haiyuzhao") default: panic("没有这个名字") }
虽然Go的panic机制相似于其余语言的异常,但panic的适用场景有一些不一样。因为panic会引发程序的崩溃,所以panic通常用于严重错误,如程序内部的逻辑不一致。
一般来讲,不该该对panic异常作任何处理,但有时,也许咱们能够从异常中恢复,至少咱们能够在程序崩溃前,作一些操做。
若是在deferred函数中调用了内置函数recover,而且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。致使panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
让咱们以语言解析器为例,说明recover的使用场景。考虑到语言解析器的复杂性,即便某个语言解析器目前工做正常,也没法确定它没有漏洞。所以,当某个异常出现时,咱们不会选择让解析器崩溃,而是会将panic异常看成普通的解析错误,并附加额外信息提醒用户报告此错误。
defer func () { if p := recover(); p != nil { fmt.Println(p) // 主动抛错 // 能够进行写日志等操做 } }() panic("主动抛错")