golang入门(2)

函数的定义看起来像这样:

type mytype int
func (p mytype) funcname(q int) (r,s int) { return 0,0 }
1、保留字 func 用于定义一个函数;
2、函数可以定义用于特定的类型,这类函数更加通俗的称呼是 method。这部分称作 receiver 而它是可选的。
3、funcname 是你函数的名字
4、int 类型的变量 q 作为输入参数。参数用 pass-by-value 方式传递,意味着它们会被复制
变量 r 和 s 是这个函数的命名返回值。在 Go 的函数中可以返回多个值。参阅第 32 页的 “多值返回”。如果不想对返回的参数命名,只需要提供类型:(int,int)。如果只有一个返回值,可以省略圆括号。如果函数是一个子过程,并且没有任何返回值,也可以省略这些内容
5、这是函数体,注意 return 是一个语句,所以包裹参数的括号是可选的。

这里有两个例子,左边的函数没有返回值,右边的只是简单的将输入返回。
func subroutine(in int) {
return
}
func identity(in int) int {
return in
}
可以随意安排函数定义的顺序,编译器会在执行前扫描每个文件。所以函数原型在 Go 中都是过期的旧物。Go 不允许函数嵌套。然而你可以利用匿名函数实现它

递归函数跟其他语言是一样的:
func rec(i int) {
if i == 10 {
return
}
rec(i+1)
fmt.Printf(“%d “, i)
}
这会打印:9 8 7 6 5 4 3 2 1 0。

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

多值返回
Go 一个非常特别的特性(对于编译语言而言)是函数和方法可以返回多个值(Python 和 Perl 同样也可以)。这可以用于改进一大堆在 C 程序中糟糕的惯例用法:修改参数的方式,返回一个错误(例如遇到 EOF 则返回 -1)。在 Go 中,Write 返回一个计数值和一个错误:“是的,你写入了一些字节,但是由于设备异常,并不是全部都写入了。”。 os 包中的 File.Write 是这样声明的:
func (file
File) Write(b []byte) (n int, err error)
如同文档所述,它返回写入的字节数,并且当 n != len(b) 时,返回非 nil的 error。这是 Go 中常见的方式。
类似的方法避免了传递指针模拟引用参数来返回值。这里有个样例函数,从字节数组的指定位上取得数值,返回这个值和下一个位置。
func nextInt(b []byte, i int) (int, int) {
x := 0
// 假设所有的都是数字
for ; i < len(b); i++ {
x = x*10 + int(b[i])-‘0’
}
return x, i
}
你可以在输入的数组中扫描数字,像这样:
a := []byte{‘1’, ‘2’, ‘3’, ‘4’}
var x int
for i := 0; i < len(a); { //没有 i++
x, i = nextInt(a, i)
println(x)
}

命名返回值
Go 函数的返回值或者结果参数可以指定一个名字,并且像原始的变量那样使用,就像输入参数那样。如果对其命名,在函数开始时,它们会用其类型的零值初始化;如果函数在不加参数的情况下执行了 return 语句,结果参数的当前值会作为返回值返回。用这个特性,允许(再一次的)用较少的代码做更多的事。
名字不是强制的,但是它们可以使得代码更加健壮和清晰: 这是文档。例如命名 int 类型的 nextPos 返回值,就能说明哪个代表哪个。
func nextInt(b []byte, pos int) (value, nextPos int) { // }
由于命名结果会被初始化并关联于无修饰的 return,它们可以非常简单并且清晰。这里有一段 io.ReadFull 的代码,很好的运用了它:
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
}

延迟代码
假设有一个函数,打开文件并且对其进行若干读写。在这样的函数中,经常有提前返回的地方。如果你这样做,就需要关闭正在工作的文件描述符。这经常导致产生下面的代码:
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 后指定的函数会在函数退出前调用。
上面的代码可以被改写为下面这样。将 Close 对应的放置于 Open 后,能够使函数更加可读、健壮。
func ReadWrite() bool {
file.Open(“file”)
defer file.Close() file.Close() 被添加到了 defer 列表
// 做一些工作
if failureX {
return false Close() 现在自动调用
}
if failureY {
return false 这里也是
}
return true
}
可以将多个函数放入 “延迟列表”中
for i := 0; i < 5; i++ {
defer fmt.Printf(“%d “, i)
}
延迟的函数是按照后进先出(LIFO)的顺序执行,所以上面的代码打印: 4 3 2 1 0。
利用 defer 甚至可以修改返回值,假设正在使用命名结果参数和函数符号
func f() (ret int) { //ret 初始化为零
defer func() {
ret++//ret 增加为 1
}()
return 10//返回的是 11 而不是 10!
}

变参
接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
func myfunc(arg …int) {}
arg … int 告诉 Go 这个函数接受不定数量的参数。注意,这些参数的类型全部是 int。在函数体中,变量 arg 是一个 int 类型的 slice:
for _, n := range arg {
fmt.Printf(“And the number is: %d\n”, n)
}
如果不指定变参的类型,默认是空的接口 interface{}(参阅第 6 章)。假设有另一个变参函数叫做 myfunc2,下面的例子演示了如何向其传递变参:
func myfunc(arg …int) {
myfunc2(arg…) 按原样传递
myfunc2(arg[:2]…) 传递部分
}

函数作为值
就像其他在 Go 中的其他东西一样,函数也是值而已。它们可以像下面这样赋值给变量:
func main() {
a := func() { 定义一个匿名函数,并且赋值给 a
println(“Hello”)
} 这里没有 ()
a() 调用函数
}
函数作为值,也会被用在其他一些地方,例如 map。这里将整数转换为函数:
var xs = map[int]func() int{
1: func() int { return 10 },
2: func() int { return 20 },
3: func() int { return 30 }, //必须有逗号
//
}

回调
当函数作为值时,就可以很容易的传递到其他函数里,然后可以作为回调。首先定义一个函数,对整数做一些 “事情”:
这个函数的标识是 func printit(int),或者没有函数名的:func(int)。创建新的函数使用这个作为回调,需要用到这个标识:
func callback(y int, f func(int)) { //f 将会保存函数
f(y) //调用回调函数 f 输入变量 y
}

恐慌(Panic)和恢复(Recover)
Go 没有像 Java 那样的异常机制:不能抛出一个异常。作为替代,它使用了恐慌和恢复(panic-and-recover)机制。一定要记得,这应当作为最后的手段被使用,你的代码中应当没有,或者很少的令人恐慌的东西。这是个强大的工具,明智的使用它。

Panic
是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数 F 调用 panic,函数 F 的执行被中断,并且 F 中的延迟函数会正常执行,然后 F 返回到调用它的地方。在调用的地方, F 的行为就像调用了 panic。这一过程继续向上,直到程序崩溃时的所有goroutine 返回。恐慌可以直接调用 panic 产生。也可以由运行时错误产生,例如访问越界的数组。

Recover
是一个内建的函数,可以让进入令人恐慌的流程中的 goroutine 恢复过来。recover 仅在延迟函数中有效。在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果。如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。

这个函数检查作为其参数的函数在执行时是否会产生 panic:
unc throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f()
return
}
1.定义一个新函数 throwsPanic 接受一个函数作为参数,参看 “函数作为值”。当这个函数产生 panic,就返回 true,否则返回 false;
2.定义了一个利用 recover 的 defer 函数,如果当前的 goroutine 产生了 panic,这个 defer 函数能够发现。当 recover() 返回非 nil 值,设置 b 为 true;
3.调用作为参数接收的函数。
4.返回 b 的值。由于 b 是命名返回值