1.介绍
Go 里面有三种类型的函数:
- 普通的带有名字的函数
- 匿名函数或者lambda函数
- 方法
除了main()、init()函数外,其它所有类型的函数都可以有参数与返回值。函数参数、返回值以及它们的类型被统称为函数签名。
函数被调用的基本格式如下:
pack1.Function(arg1, arg2, …, argn)
假设 f1 需要 3 个参数 f1(a, b, c int),同时 f2 返回 3 个参数 f2(a, b int) (int, int, int),就可以这样调用 f1:f1(f2(a, b))。
如果需要申明一个在外部定义的函数,你只需要给出函数名与函数签名,不需要给出函数体:
func flushICache(begin, end uintptr) // implemented externally
函数也可以以申明的方式被使用,作为一个函数类型,就像:
type binOp func(int, int) int
函数是一等值(first-class value):它们可以赋值给变量,就像 add := binOp 一样。
函数值(functions value)之间可以相互比较:如果它们引用的是相同的函数或者都是 nil 的话,则认为它们是相同的函数。
函数不能在其它函数里面声明(不能嵌套),不过我们可以通过使用匿名函数来破除这个限制。
2.函数参数与返回值
1)我们通过 return 关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 return 或 panic结尾。
2)函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:func f(int, int, float64)
3)按值传递和引用传递:
按值传递Function(arg1)
引用传递Function(&arg1)
4)命名的返回值:
当需要返回的时候,我们只需要一条简单的不带参数的return语句。需要注意的是,即使只有一个命名返回值,也需要使用 () 括起来
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return
}
尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂
5)空白符
空白符用来匹配一些不需要的值,然后丢弃掉 i1, _, f1 = ThreeValues()
6)改变外部变量
传递指针给函数不但可以节省内存(因为没有复制变量的值)
3.传递变长参数
如果函数的最后一个参数是采用 …type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。func myFunc(a, b, arg ...int) {}
这个函数接受一个类似某个类型的 slice 的参数,该参数可以通过 for 循环结构迭代
这个函数接受一个类似某个类型的 slice 的参数(详见第 7 章),该参数可以通过第 5.4.4 节中提到的 for 循环结构迭代
package main
import "fmt"
func main(){
x := min(1, 3, 2, 0)
fmt.Println(x)
arr := []int{7, 9, 3, 5, 1}
x = min(arr...)
fmt.Println(x)
}
func min(a ...int) int {
if len(a) == 0 {
return 0
}
min := a[0]
for _, v := range a {
if v < min {
min = v
}
}
return min
}
一个接受变长参数的函数可以将这个参数作为其它函数的参数进行传递:
变长参数可以作为对应类型的 slice 进行二次传递。
func F1(s ...string) {
F2(s...)
F3(s)
}
func F2(s ...string) { }
func F3(s []string) { }
如果变长参数的类型并不是都相同
1.使用结构
type Options struct {
par1 type1,
par2 type2,
...
}
F1(a, b, Options {})
F1(a, b, Options {par1:val1, par2:val2})
2.使用空接口
func typecheck(..,..,values … interface{}) {
for _, value := range values {
switch v := value.(type) {
case int: …
case float: …
case string: …
case bool: …
default: …
}
}
}
4.defer和追踪
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源。
当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出)
1)解锁一个加锁的资源
mu.Lock()
defer mu.Unlock()
2)打印最终报告
printHeader()
defer printFooter()
3)关闭数据库链接
// open a database connection
defer disconnectFromDB()
4)关闭文件流
// open a file
defer file.Close()
5.内置函数
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数
close 用于管道通信
new make
copy apend
panic recover
print println
complex real image
6.将函数作为参数
函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
7.闭包
匿名函数,例如:func(x, y int) int { return x + y }。
func() {
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()
匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。
这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装。
8.闭包的应用
9.使用闭包调试
10.计算函数执行时间
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
11.通过内存缓存来提升性能
12.递归函数