有两个简单的函数例子:

1
2
func subroutine(in int ) { return }
func identity(in int ) int { return in }

作用域

在 Go 中,定义在函数外的变量是全局的,那些定义在函数内部的变量,对于函数来说是局部的。如果一个局部变量与一个全局变量有相同的名字,在函数执行的时候,局部变量将覆盖全局变量。

看下面的三个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
var a = 6
func main() {
p()
q()
p()
}
func p() {
println(a)
}
func q() {
a := 5 //定义
println(a)
}

输出打印:656
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
var a = 6
func main() {
p()
q()
p()
}
func p() {
println(a)
}
func q() {
a = 5 //赋值
println(a)
}

输出打印:655
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
var a int
func main() {
a = 5
println(a)
f()
}
func f() {
a := 6
println(a)
g()
}
func g() {
println(a)
}

输出打印:565

命名返回值

Go 函数的返回值或者结果参数可以指定一个名字。

名字不是强制的,但是它们可以使得代码更加健壮和清晰。

1
2
3
4
5
6
7
8
9
func ReadFull(r Reader, buf []byte) (n int , err error ) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:len(buf)]
}
return
}

延迟代码

有如下一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}

上面的例子当中用到了提前返回,但是每次返回之前有需要关闭文件,导致了代码的重复。

Go 有了 defer 语句。在 defer 后指定的函数会在函数退出前调用。

1
2
3
4
5
6
7
8
9
10
11
12
func ReadWrite() bool {
file.Open("file")
defer file.Close() // file.Close() 被添加到了defer 列表
// 做一些工作
if failureX {
return false // Close() 现在自动调用
}
if failureY {
return false //这里也是
}
return true // And here
}

可以将多个函数放入“延迟列表”中,延迟的函数是按照后进先出(LIFO)的顺序执行:

1
2
3
4
5
for i := 0 ; i < 5 ; i++ {
defer fmt.Printf("%d ", i)
}

打印:4 3 2 1 0

利用 defer 甚至可以修改返回值,在这个(匿名)函数中,可以访问任何命名返回参数:

1
2
3
4
5
6
func f() (ret int ) {   // ret 初始化为零
defer func() {
ret++ // ret 增加为 1
}()
return 0 // 返回的是 1 而不是 0
}

变参

定义函数使其接受变参:

1
func myfunc(arg ... int ) { }

在函数体中,变量 arg 是一个 int 类型的 slice。

传递变参如下:

1
2
3
4
func myfunc(arg ... int ) {
myfunc2(arg...) // 按原样传递
myfunc2(arg[:2]...) // 传递部分
}

函数作为值

可以将一个函数赋值给一个变量:

1
2
3
4
5
6
func main() {
a := func() {
println("Hello")
}
a()
}

回调

由于函数也是值,所以可以传递到其他函数当中,作为回调。

1
2
3
4
5
6
7
func printit(x int ) {
fmt.Printf("%v\n", x)
}

func callback(y int , f func( int )) {
f(y)
}

panic-and-recover

Go 当中没有类似 JAVA 的异常机制。所以使用 panic-and-recover 机制。

panic:是一个内建函数,可以中断原有的控制流程。当函数 F 调用 panic,函数 F 的执行被中断,并且 F 中的延迟函数会正常执行,然后 F 返回到调用它的地方。panic 可以直接调用,也可以在运行时产生。

recover:是一个内建的函数,可以让进入令人恐慌的流程中的 goroutine 恢复过来。recover 仅在延迟函数中有效。

如下例子:

1
2
3
4
5
6
7
8
9
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover() ; x ! = nil {
b = true
}
}()
f()
return
}

【课后练习】

go-leran

—EOF—