go程序设计语言第五章-函数

函数

函数的类型称为函数签名: func signature。
两个函数有相同的参数类型列表和相同的返回结果类型列表,则类型即签名相同。

go没有默认参数和带名称参数。

函数参数通过值复制,因此函数接收的是每个参数的副本。

go的垃圾回收会清理无用的内存,但不会清理系统资源,如打开的文件描述符。

在返回值带有name的函数中,return语句可以省略,称为bare return。

strings.Contains strconv.FormatBool

一个error类型包含一个string msg,可以通过fmt.Println(err)或者fmt.Printf(“%v”, err)获得
通常函数返回非nil错误则其他的返回结果该被忽略。不过有些函数在返回错误的情况下,
其他结果同样有意义。例如Read读取文件,返回能够读取的字节数和error错误。

Go’s approach sets it apart from many other languages in which failures are reported using
exceptions, not ordinary values.
Although Go does have an exception mechanism of sorts, as
we will see in Section 5.9, it is used only for reporting truly unexpected errors that indicate a
bug, not the routine errors that a robust program should be built to expect.

在Go中,函数运行失败时会返回错误信息,这些错误信息被认为是一种预期的值而非异常(exception),
这使得Go有别于那些将函数运行失败看作是异常的语言。
虽然Go有各种异常机制,但这些机制仅被使用在处理那些未被预料到的错误,即bug,
而不是那些在健壮程序中应该被避免的程序错误。对于Go的异常机制我们将在5.9介绍。

函数不可比较,不能作为map的key。

More importantly, functions defined in this way have access to the entire lexical environment,
so the inner function can refer to variables from the enclosing function, as this example shows:

匿名函数可以访问整个词法域,因此内部函数可以指向外层的变量。

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"
}

square函数返回一个类型为func() int的函数。调用squares会创建一个本地变量x然后返回一个匿名函数。
每次调用匿名函数,增加x的值返回它的平方。对squares的第二次调用将创建第二个变量x并返回一个新的匿名函数。

这个例子表明函数值不仅有代码并且有状态。
这些隐藏的变量引用正是为什么我们将函数定义为引用类型且不可比较。
这种类型函数称为closures闭包。

通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。

Caveat: Capturing Iteration Variables

// correct
var rmdirs []func()
for _, d := range tempDirs() {
	dir := d // NOTE: necessary!
	os.MkdirAll(dir, 0755) // creates parent directories too
	rmdirs = append(rmdirs, func() {
		os.RemoveAll(dir)
	})
}

//incorrent
var rmdirs []func()
for _, dir := range tempDirs() {
	os.MkdirAll(dir, 0755)
	rmdirs = append(rmdirs, func() {
		os.RemoveAll(dir) // NOTE: incorrect!
	})
}


// ...do some work...

for _, rmdir := range rmdirs {
	rmdir() // clean up
}

在上面的incorrent例子中,range循环中,dir是一个可寻址的变量,不是一个具体的值。
每次循环都更新此变量的值,都循环结束后,dir变为最后一次循环的值。
在rmdirs执行时,获取到的dir是同一个值。
当在循环内部重新声明新变量后,append的值则不同。

这种情况不只是发生在loop循环,如下

var rmdirs []func()
dirs := tempDirs()
for i := 0; i < len(dirs); i++ {
	os.MkdirAll(dirs[i], 0755) // OK
	rmdirs = append(rmdirs, func() {
		os.RemoveAll(dirs[i]) // NOTE: incorrect!
	})
}

循环变量的捕获主要发生在go语句和defer语句中,是因为他们都会延迟调用函数直到循环结束。

5.7 variadic functions 变长函数

最后一个参数后跟…,func sum(val ...int) int 函数内部此参数为slice类型.
隐式地,调用者创建一个数组,将所有参数复制进去,然后传一个此数组的切片给函数。
也可以直接传给函数切片,只是需要将在此切片后加省略号。

sum(1,2,3,4)
values := []int{1,2,3,4}
sum(values...)

尽管在函数内部…int参数被认为是slice,但它和传统的func g([]int) {}是两个不同的类型

5.8 Deferred Function Calls

Syntactically, a defer statement is an ordinary function or method call prefixed by the
keyword defer. The function and argument expressions are evaluated when the statement is
executed, but the actual call is deferred until the function that contains the defer statement
has finished, whether normally, by executing a return statement or falling off the end, or
abnormally, by panicking .

语法上,一个defer statement是一个有着关键字defer前缀的常规的函数或方法调用。
当语句执行时,函数和参数表达式会被评估,但只有当包含defer语句的函数结束时,才会
发生真正的函数调用,通常是当执行到return语句或者执行到末尾或者是panic异常。

  • defer后跟函数或方法调用
  • defer后的statement会立即计算函数和参数表达式的值,但外部函数执行结束后才会真正执行函数调用
    (也就是说,defer函数中的参数表达式是立即执行的)
  • return语句或正常结束或panic都会导致defer的执行

任意数量的defer语句都可以,执行顺序与注册顺序相反。

defer statement经常用在成对的操作如open/close,connect/disconnect, lock/unlock
来确保资源被释放。
defer statement也可以用在成对的on entry和on exit操作上。

func bigSlowOperation() {
	defer trace("bigSlowOperation")()
	time.Sleep(10 * time.Second)
}

func trace(msg string) func() {
	start := time.Now()
	log.Printf("enter %s", msg)
	return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) }
}

bigSlowOperation函数内立即调用,执行on entry操作,返回一个函数,执行此函数,就是执行
on exit操作。通过这样延迟调用返回的函数(即trace返回的函数),可以在单条语句中记录一个函数的所有
进入点和出口点,甚至可以在进出两个点之前传值,如start time。
但是不要忘记defer语句最后的小括号,否则在函数结束时才执行进入操作,且退出操作永远不会执行
(因此这时候延迟执行的是trace函数了)。
【这个例子实际上是说明defer语句中函数被立即评估,形如g(x)(y)样式的defer语句,在(y)前的语句g(x)或者g(x1)(x2)…都肯定最终返回一个函数,而执行返回这个参数的操作也就是g(x1)(x2)…(xn)是在defer时就立即执行的,其最终会返回一个依赖于(y)参数的函数,而这个最终的函数调用是在包裹defer语句的函数结束后才调用。】

defer函数在return语句已经更新函数的结果变量后才执行。
而因为一个匿名函数可以获取包裹它的函数内部变量,包括命名的结果变量,因此一个匿名的defer函数可以
获取它外部函数的结果。

func double(x int) (result int) {
	defer func() {
		fmt.Printf("double(%d) = %d\n", x, result)
	}()
	return x + x
}

这里通过将返回结果命名为result,并增加一个匿名的defer语句,可以使得每次调用函数都能打印参数和结果。
这个技巧可以用在拥有许多返回值的函数上。

defer的匿名函数甚至可以改变包裹它的函数的返回值【相当于return之后又执行了defer,而defer内又修改了result】。

func triple(x int) (result int) {
	defer func() { result += x }()
	return double(x)
}
fmt.Println(triple(4)) // "12"

因为一个defer函数只有在函数执行结束后才会执行,因此包裹在loop循环中的defer要非常小心。

以下为go官方文档对defer的介绍:
The Go Programming Language Specification

A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
DeferStmt = "defer" Expression
The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.

For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)

lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

【摘要】
1 defer 的function value and parameters在defer时就计算
2 defer函数的执行在return之后但在返回给调用者之前,因此它可以访问和修改返回命名的返回结果
3 因为defer函数执行是在外围函数返回之后,因此一个nil value的defer 函数,只有在真正调用时才会panic而不是在defer时。
4 defer的函数返回值会被丢弃

5.9 Panic

Go的类型系统在编译时会捕获很多错误,但另外的一些错误,如数组索引超出、nil指针解引用,都要求在运行时检查。
当运行时检查到这些错误,会报出panic。

During a typical panic, normal execution stops, all deferred function calls in that goroutine are
executed, and the program crashes with a log message. This log message includes the panic
value, which is usually an error message of some sort, and, for each goroutine, a stack trace
showing the stack of function calls that were active at the time of the panic.

在一个典型的panic中,常规执行停止,此gorountine内的deferred函数被调用执行,程序会崩溃且抛出log信息。
log信息包括了panic value,它通常是一个错误信息和针对每个goroutine在发生panic时的函数调用栈的stack trace。

并不是所有的panic都来自于运行时。内置的panic函数也可以直接调用;它接受任意值作为参数。
通常当一种不可能的状况发生时才触发panic,如case语句中一种不可能的情况则使用panic。

使用panic进行函数前置条件的判断是一个实践,但肯容易被滥用。除非你能提供更多的错误信息或者
能立即探测到错误,否则没有必要进行判断,因为运行时会帮你检查。

func Reset(x *Buffer) {
	if x == nil {
		panic("x is nil") // unnecessary!
	}
	x.elements = nil
}

尽管Go的panic机制像其他语言的exception,但panic的适用场景有些不同。
由于panic会引起程序的崩溃,因此panic一般用于严重错误,如程序内部的逻辑不一致。
勤奋的程序员认为任何崩溃都表明代码中存在漏洞,所以对于大部分漏洞,我们应该使用Go提供的错误机制,
而不是panic,尽量避免程序的崩溃。
在健壮的程序中,任何可以预料到的错误,如不正确的输入、错误的配置或是失败的IO操作都应该被优雅的处理,
最好的处理方式,就是使用Go的错误机制。

有时当调用者知道一定不会有错误时,就可以写一个前缀Must的类似函数,将返回值中的err去掉,
转为函数中判断如果有err,则panic。这样能够减轻调用者判断err的额外负担。

当panic发生时,所有defer的函数按照栈先入后出的原则依次调用,然后程序崩溃。
也可以使函数从panic处recover从而避免程序的终止。

go的panic机制将在函数调用栈被释放之前执行已经defer的函数。

5.10 Recover

通常来说,对panic应该不做任何处理,但并不总是这样。有可能需要按某种方式恢复,或者至少在退出前清理现场。
例如,web服务器遇到一个非预期的问题应该关闭连接,而不是让客户端仍然保持连接,在开发环境中,
也应该将错误回复给客户端。

如果包裹在defer内部的recover函数被调用,包裹defer的函数出现panic,recover会终止panic当前状态,
返回panic value。出现panic的函数不会继续运行,但会正常返回。如果recover在其他时候被调用,则返回nil。

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

defer 函数中从panic恢复,使用panic value值构建一个error msg,更为完善的方法是
使用runtime.Stack来包含整个call statck。defer 函数然后复制给err result,正常返回给调用者。

随意地使用recover从panic恢复不是一个好的做法,因为包级别的变量在panic之后是不确定的。
并且,不使程序崩溃而只是将其记录在log文件中,随意的recover将会导致bug被忽略。

不要恢复其他包引起的panic,共有的api应该将函数失败作为err返回,而不是panic。

应该有选择性的recover,也就是说,恢复那些希望恢复的panic。
可以将panic value设置为特殊类型,在recover时对panic value进行检查。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值