golang函数

1.函数声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。数组

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"

2.递归

函数能够是递归的,这意味着函数能够直接或间接的调用自身。对许多问题而言,递归是一种强有力的技术,例如处理递归的数据结构。函数

func a(i int) (res int){
    if i == 1 {
        return i 
    }
    return i * a(i - 1)
}

fmt.Println(a(5)) // 120

3.多返回值

在Go中,一个函数能够返回多个值。google

func calculation(a,b int)(add,sub int) {
	add = a + b
    sub = a - b
}

4.错误

在Go中有一部分函数老是能成功的运行,对各类可能的输入都作了良好的处理,使得运行时几乎不会失败,除非遇到灾难性的、不可预料的状况,好比运行时的内存溢出。致使这种错误的缘由很复杂,难以处理,从错误中恢复的可能性也很低。指针

panic是来自被调函数的信号,表示发生了某个已知的bug。一个良好的程序永远不该该发生panic异常。日志

value, ok := cache.Lookup(key)
if !ok {
    // ...cache[key] does not exist…
}

4.1. 错误处理策略

当一次函数调用返回错误时,调用者有应该选择什么时候的方式处理错误。根据状况的不一样,有不少处理方式.code

resp,err := http.Get("https://www.google.com")
if err != nil {
    fmt.Println(err)
}

4.2. 文件结尾错误(EOF)

函数常常会返回多种错误,这对终端用户来讲可能会颇有趣,但对程序而言,这使得状况变得复杂。不少时候,程序必须根据错误类型,做出不一样的响应。让咱们考虑这样一个例子:从文件中读取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

5.函数值

在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错误

6.匿名函数

拥有函数名的函数只能在包级语法块中被声明,经过函数字面量(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"
}

7.可变参数

参数数量可变的函数称为为可变参数函数。典型的例子就是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
}

8.Panic异常

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通常用于严重错误,如程序内部的逻辑不一致。

9.Recover捕获异常

一般来讲,不该该对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("主动抛错")
相关文章
相关标签/搜索