八、函数
函数的声明
func name(parameter-list) (result-list) {
body
}
后置类型
eg
func add(a, b int) int {
return a + b;
}
我们的函数返回的时候可以返回错误,即函数中的错误向上抛,抛到调用这个函数的函数中,然后结束函数
1.匿名函数
即func之后没有函数名称
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
2.警告:捕获迭代变量
考虑这样一个问题:你被要求首先创建一些目录,再将目录删除。在下面的例子中我们用函数值来完成删除操作。下面的示例代码需要引入os包。为了使代码简单,我们忽略了所有的异常处理。
var rmdirs []func()
for _, d := range tempDirs() {
dir := d // NOTE: necessary!
os.MkdirAll(dir, 0755) // creates parent directories too
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir)
})
}
// ...do some work…
for _, rmdir := range rmdirs {
rmdir() // clean up
}
你可能会感到困惑,为什么要在循环体中用循环变量d赋值一个新的局部变量,而不是像下面的代码一样直接使用循环变量dir。需要注意,下面的代码是错误的。
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect!
})
}
rmdir存储的是迭代变量的地址,然而当我们迭代的时候dir的所有的数都在一个地址内,所以我们最后只能删除迭代元素的创建的最后一个文件夹,
我们如果创建一个新的变量去存储dir这个值,那么rmdir在存的时候会存新的变量的地址,存的地址就会各不相同,删除的时候就能把他们全部删除
这个问题不仅存在基于range的循环,在下面的例子中,对循环变量i的使用也存在同样的问题:
var rmdirs []func()
dirs := tempDirs()
for i := 0; i < len(dirs); i++ {
os.MkdirAll(dirs[i], 0755) // OK
rmdirs = append(rmdirs, func() {
os.RemoveAll(dirs[i]) // NOTE: incorrect!
})
}
3.可变参数
函数参数个数可变就称为可变参数
我们在声明函数的时候在最后一个参数类型之前加上...这表示这个函数会接受任意数量该类型的参数
...类似于js中的结构
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为[] int的切片。sum可以接收任意数量的int型参数:
fmt.Println(sum()) // "0"
fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调用函数。如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符。下面的代码功能与上个例子中最后一条语句相同。
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
使用可变参数编写max函数(min函数类似)
package main
import (
"errors"
"fmt"
)
func max(values ...int) (int, error) {
if len(values) == 0 {
return 1, errors.New("参数个数小于两个")
}
MaxVal := values[0]
for _, val := range values {
if val > MaxVal {
MaxVal = val
}
}
return MaxVal, nil
}
func main() {
x, err := max()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(x)
}
4.Defer函数
首先先说Defer的特性
- 在函数的最后执行(defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁)
- 因为Defer函数总是在最后执行,所以我们不需要在最后写直接卸载请求资源语句的下面
- 如果有多个Defer语句那么他们会呈现栈的方式去执行,根据声明的时间从后往前执行
使用的例子
利用defer机制去关闭两个通道
func main() {
src := make(chan int)
dist := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i <= 9; i++ {
src <- i
}
}()
go func() {
defer close(dist)
for i := range src {
dist <- i * i
}
}()
for v := range dist {
fmt.Println(v)
}
}
利用defer机制让WaitGroup中的计数器减一
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
fmt.Println(j)
}(i)
}
wg.Wait()
}
处理锁的机制
var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}
5.闭包
和JavaScript中的匿名函数十分的相似
下面是一个闭包的程序
package main
import "fmt"
func main() {
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d ", i) } //此例子中只是为了演示匿名函数可分配不同的内存地址,在现实开发中,不应该把该部分信息放置到循环中。
g(i)
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}
输出
0 - g is of type func(int) and has value 0x681a80
1 - g is of type func(int) and has value 0x681b00
2 - g is of type func(int) and has value 0x681ac0
3 - g is of type func(int) and has value 0x681400
6.应用闭包:将函数作为返回值
package main
import "fmt"
func main() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 2:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}
func Add2() func(b int) int {
return func(b int) int {
return b + 2
}
}
func Adder(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
输出
Call Add2 for 3 gives: 5
The result is: 5
下面一个略微不同的实现
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1), " - ")
fmt.Print(f(20), " - ")
fmt.Print(f(300))
// 输出 1 - 21 - 321
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
我们可以看到在闭包中x临时变量的值会被保存即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321:闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。
使用闭包写一个斐波那契的函数,利用了闭包中临时变量值被保存的特性
package main
import "fmt"
func main() {
num := 10
f := fib()
fmt.Println(f(num))
}
func fib() func(int) int {
var g int
n, m := 1, 1
return func(i int) int {
if i < 2 {
return 1
} else {
for j := 2; j < i; j++ {
g = n + m
n = m
m = g
}
return g
}
}
}