函数
Go 函数不支持嵌套,重载和默认参数
但支持以下特性:
- 无需声明原型,不定长度变参,多返回值,命名返回值参数,匿名函数,闭包
定义函数使用关键字func
,且左大括号不能另起一行
函数也可以作为一种类型使用
无需声明原型
1 | func funcTest1(a int, b string) (int, string, int) { |
多参数同类型
参数
1 | func funcTest2(a, b, c int) { |
返回值
1 | func funcTest2(a, b, c int) (a1, b1, c1 int) { |
return
与返回声明中的参数名一样就可以省略不用写,不过一般情况下,按照编码要求还是需要写得
1 | func funcTest2(a, b, c int) (a1, b1, c1 int) { |
不定长度变参
java 中参数可变的例子
1 | private static int sumUp(int... values) { |
在go中也可以
1 | func funcTest3(a ...int) int { |
...[type]
必须是最后一个参数,是一个slice
以下是错误的
1 | func funcTest(a ...int, b string) |
正确
1 | func funTest(b string, a ...int) |
...[type]
可变参数可以看作是一个slice
,但是与slice
不同的是,不是引用类型,实际上得到是一个值拷贝。
如果参数是slice
,是拷贝slice
的内存地址,而不是值。
1 | func funcTest5(s []int) { |
输出
1 | before: [1 2 3] |
一般类型都是值拷贝,如果需要影响到传入的值,需要传入值的地址。
1 | - func funcTest5(s []int) { |
输出
1 | before: [1 2 3] 1 |
函数也可以作为一种类型使用
这个特性与JavaScript中差不多,定义一个变量等于函数,使用这个变量相当于使用这个函数。在Go中一切皆类型
1 | func funcTest6() { |
输出
1 | hello everyone |
匿名函数
1 | func main() { |
没有命令函数名称。是一个代码块。
闭包
1 | func funcTest7(x int) func(int) int { |
输出
1 | func test 7 10 0xc00001a098 |
3次输出x
的地址是一样的。
defer
执行方式类似其他语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行(先进后出,栈堆)。即使函数发生严重错误也会执行,支持匿名函数的调用。通常用于资源清理,文件关闭,解锁以及记录时间等操作。通过与匿名函数配合可在return
之后修改函数计算结果。如果函数体内某个变量作为defer
时匿名函数的参数,则在定义defer
时即已经获得了拷贝,否则引用某个变量的地址。。
Go 中没有异常机制,但是有 panic/recover
模式来处理错误
panic
可以在任何地方引发,但是recover
只有在defer
调用的函数中有效
相反顺序
1 | func main() { |
输出
1 | a |
按照调用顺序的相反顺序逐个执行
1 | func main() { |
输出
1 | 2 |
闭包
在这个匿名函数中,i
被当作一个地址的引用,引用这个局部变量,当循环结束时,i = 3 。在main函数结束后,开始执行defer
中的代码。
1 | for i := 0; i < 3; i++ { |
输出
1 | 3 |
可以改成这样:
1 | for i := 0; i < 3; i++ { |
输出
1 | 2 |
panic
1 | func funcTest8(){ |
输出
1 | func test 8 |
并没有继续往下执行到test10
recover()
1 | func funcTest9() { |
需要在panic
前面加入以上代码,输出
1 | func test 8 |
练习
1 | func main() { |
输出
1 | closure i = 4 |
fs[i]()
中的i
,因为使用的是外部的i
,实际上调用外部 i
的地址,当循环体结束时,i = 4
,所以输出closure i = 4
defer 使用的匿名函数与上面一样,是闭包的思想。