[Go语言基础语法 (四,完结) | 青训营笔记]

61 阅读4分钟

八、函数

函数的声明

 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
         }
     }
 }