Go语言学习笔记——函数

函数声明

每个函数都包含一个名字、一个形参列表、一个可选的返回列表以及函数体:

func name(parameter-list) (result-list) {
    body
}

当函数存在返回列表时,必须显示地以return语句结束,除非函数明确不会走完整个执行流程,
比如在函数中抛出宕机异常挥着函数体内存在一个没有break退出条件的无限for循环

函数的类型称为函数签名,当两个函数拥有相同的形参列表和返回值列表时,认为这两个函数的类型或是签名是相同的,形参和返回值的名字不会影响函数类型

Go语言不支持默认参数值,也不支持指定实参名

实参是按值传递的,所以函数接收到的是每个实参的副本,如果提供的是实参包含引用类型,比如指针、slice、map、函数或者通道,那么当函数使用形参变量时就有可能会间接地修改实参变量

如果一个函数的声明没有函数体,那么说明这个函数使用除了Go以外的语言实现,例如:

package math

func Sin(x float64) float64 // 使用汇编语言实现

递归

函数可以递归调用,Go语言的实现使用了可变长度的栈,栈的大小会随着使用而增长,可到1GB左右的上限

多返回值

函数可以返回不止一个结果

错误

与许多其他语言不同的是,Go语言通过使用普通的值而非异常来报告错误,尽管Go语言有异常机制,后面讲到,但是Go语言的异常只是针对程序bug导致的预料之外的错误,而不能作为常规的错误处理方法出现在程序中

这样做的原因是异常会陷入带有错误信息的控制流去处理它,通过会导致预期外的结果:
错误会以难以理解的栈跟踪信息报告给最终用户,这些信息大都是关于程序结构方面的而不是简单明了的错误信息

错误处理策略

当一个函数调用返回一个错误时,调用者应当负责检查错误并采取合适的处理应对

  1. 将错误传递下去
  2. 构建一个新的错误信息,其中包含我们需要的所有相关和解析的错误信息
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
	return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}

fmt.Errorf使用fmt.Sprintf函数格式化一条错误信息并且返回一个新的错误值
3. 对于不固定或者不可预测的错误,在短暂的间隔后对操作进行重试是合乎情理的,超出一定的重试次数和限定的时间后再报错退出
4. 如果依旧不能顺利进行下去,调用者能够输出错误然后优雅地停止程序
5. 在一些错误情况下,只记录下错误信息,然后程序继续运行

函数变量

函数类型的零值是nil,调用一个空的函数值变量将导致宕机

函数变量可以和空值比较,但它们本身不可比较,所以不可以互相进行比较或者作为键值出现在map中

匿名函数

命名函数只能在包级别的作用域进行声明,但我们能够使用函数字面量在任何表达式内指定函数变量。
函数字面量就像函数声明,但在func关键字后面没有函数的名称。它是一个表达式,它的值称作匿名函数。

函数字面量在我们需要使用的时候才定义

strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")

变长函数

变长函数被调用的时候可以有可变的参数个数

在参数列表最后的类型名称之前使用省略号表示一个变长函数,调用这个函数的时候可以传递该类型任意数目的参数

func sum(vals ...int) int {
	total := 0
	for _, val := range vals {
		total += val
	}
	return total
}

// 调用
fmt.Println(sum())           // "0"
fmt.Println(sum(3))          // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...))  // "10"

尽管...int参数就像函数体内的slice,但变长函数的类型和一个带有普通slice参数的函数的类型不相同

func f(...int) {}
func g([]int) {}

fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"

延迟函数调用

一个defer语句就是一个普通的函数或方法调用,在调用之前加上关键字defer。函数和参数表达式或在语句执行时求值,
但是无论是正常情况下,执行return语句或函数执行完毕,还是在不正常情况下,比如宕机,实际的调用推迟到包含defer语句的函数结束后才执行

defer语句没有限制使用次数,执行的时候以调用defer语句顺序的倒序执行

defer语句经常用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁

宕机

Go语言的类型系统会捕获许多编译时的错误,但有些其他错误,比如数组越界或者解引用控指针都需要在运行时进行检查,
当Go语言运行时检测到这些错误,它就会发生宕机

一个典型的宕机发生时,正常的程序执行会终止,goroutine中的所有延迟函数会执行,然后程序会异常退出并留下一条日志消息。
日志消息包含宕机的值,代表某种错误消息,每一个goroutine都会在宕机的时候显示一个函数调用的栈跟踪信息

恢复

如果内置的recover函数在延迟函数的内部调用,而且这个包含defer语句的函数发生宕机,recover会终止当前的宕机状态并且返回宕机的值。
函数不会从之前宕机的地方继续运行而是正常返回。如果recover在其他任何情况下运行则它没有任何效果且返回nil

func Parse(input string) (s *Syntax, err error) {
	defer func() {
		if p := recover(); p != nil {
			err = fmt.Errorf("internal error: %v", p)
		}		
	}()
	// ...解析器...
}

你可能感兴趣的:(Go)