函数

寒江蓑笠翁大约 8 分钟

函数

在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类型的参数ab,返回值类型为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