函数
函数
在Go中,函数是一等公民,函数是Go最基础的组成部分,也是Go的核心。例如启动函数main
package main
import "fmt"
func main() {
fmt.println("Hello 世界!")
}
声明
函数的声明格式如下
func 函数名([参数列表]) [返回值] {
函数体
}
函数可以直接通过func
关键字来声明,也可以声明为一个字面量,也可以作为一个类型。
// 直接声明
func DoSomething() {
}
// 字面量
var doSomthing func()
// 类型
type DoAnything func()
函数签名由函数名称,参数列表,返回值组成,下面是一个完整的例子
func Sum(a, b int) int {
return a + b
}
函数名称Sum
,有两个int
类型的参数a
,b
,返回值类型为int
。
提示
在Go中,不支持函数重载。
参数
Go中的函数参数可以有名称,也可以没有名称。例如在声明一个函数字面量时可以省略名称,但是在赋值时依旧需要名称。
var sum func(int,int) int
sum = func(a int, b int) int {
return a + b
}
对于一些类型相同且相邻的参数而言,可以只声明一次类型。例如
// a,b,c都是int类型的参数,所以只需要声明一次类型
func max(a, b, c int) int {
if a < b {
a, b = b, a
}
if a < c {
a, c = c, a
}
return a
}
变长参数可以接收0个或多个值,必须声明在参数列表的末尾。
func max(args ...int) int {
max := math.MinInt64
for _, arg := range args {
if arg > max {
max = arg
}
}
return max
}
提示
Go中的函数参数是传值传递,即在传递参数时会拷贝实参的值
返回值
Go中的返回值也可以有名称,并且可以拥有多个返回值。当函数只有一个返回值且没有名称时如下例
func Sum(a, b int) int {
return a + b
}
如果有多个返回值则需要加上括号,例如
func Div(a, b float64) (float64, error) {
if a == 0 {
return math.NaN(), errors.New("0不能作为被除数")
}
return a / b, nil
}
如果返回值有名称,也需要加上括号。
func Sum(a, b int) (ans int) {
return a + b
}
也可以如下,不管返回值是否命名,优先级最高的永远都是return
关键字后跟随的值。
func Sum(a, b int) (ans int) {
ans = a + b
return // 等价于 return ans
}
匿名函数
匿名函数只能在函数内部存在,匿名函数可以简单理解为没有名称的函数,例如
func main() {
func(a, b int) int {
return a + b
}(1, 2)
}
或者当函数参数是一个函数类型时,这时名称不再重要,可以直接传递一个匿名函数
func main() {
DoSum(1, 2, func(a int, b int) int {
return a + b
})
}
func DoSum(a, b int, f func(int, int) int) int {
return f(a, b)
}
闭包
闭包(Closure)这一概念,在一些语言中又被称为Lamda表达式,经常与匿名函数一起使用,函数 + 环境引用 = 闭包
。看一个例子:
func main() {
sum := Sum(1, 2)
fmt.Println(sum(3, 4))
fmt.Println(sum(5, 6))
}
func Sum(a, b int) func(int, int) int {
return func(int, int) int {
return a + b
}
}
3
3
在上述代码中,无论传入什么数字,输出结果都是3,稍微修改一下代码
func main() {
sum := Sum(5)
fmt.Println(sum(1, 2))
fmt.Println(sum(1, 2))
fmt.Println(sum(1, 2))
}
func Sum(sum int) func(int, int) int {
return func(a, b int) int {
sum += a + b
return sum
}
}
8
11
14
匿名函数引用了参数sum
,即便Sum
函数已经执行完毕,虽然已经超出了它的生命周期,但是对其返回的函数传入参数,依旧可以成功的修改其值,这一个过程就是闭包。事实上参数sum
已经逃逸到了堆上,只要其返回值函数的生命周期没有结束,就不会被回收掉。
利用这一特性,可以非常简单的实现一个求费波那契数列的函数,代码如下
func main() {
fib := Fib()
for i := 0; i < 10; i++ {
fmt.Println(fib())
}
}
func Fib() func() int {
a, b := 1, 1
return func() int {
a, b = b, a+b
return a
}
}
输出为
1
2
3
5
8
13
21
34
55
89
延迟调用
defer
关键字描述的一个匿名函数会在函数返回之前执行。
func main() {
Do()
}
func Do() {
defer func() {
fmt.Println("1")
}()
fmt.Println("2")
}
2
1
当有多个defer
语句时,会按照后进先出的顺序执行。
func main() {
Do()
}
func Do() {
defer func() {
fmt.Println("1")
}()
defer func() {
fmt.Println("2")
}()
defer func() {
fmt.Println("3")
}()
defer func() {
fmt.Println("4")
}()
fmt.Println("2")
defer func() {
fmt.Println("5")
}()
}
2
5
4
3
2
1
延迟调用通常用于释放文件资源,关闭连接等操作,另一个常用的写法是用于捕获panic
。