Golang之函数
一、函数定义
Go语言中支持函数、匿名函数和闭包,下面看一下函数的定义
// 第一个小括号当中是参数列表,
// 第二个小括号是返回值列表
func A(a int, b string) (int, string, int) {
}
// 如果abc都是int型的话,可以按照这种方法进行简写,同样的方法也适用于返回值当中
func A(a, b, c int) (int, string, int) {
}
// 无返回值
func A() {}
命名返回值
func a(x, y int) (a, b int) {
  a = x + y
  b = x - y
  return
}
func TestOne(t *testing.T) {
  t.Log(a(12, 4))   // 16, 8
}二、可变参数
import "testing"
func Snm(ops ...int) int {
ret := 0
for _, op := range ops {
ret += op
}
return ret
}
func TestTimeSpent(t *testing.T) {
t.Log(Snm(1, 3, 5, 7, 9))
}
三、函数类型和变量
使用type来定义的函数类型
// 定义了一个calc类型,它是一种函数类型,
// 这种函数接收两个int类型的参数,返回一个int类型的返回值。
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
// 使用
func TestFuncOne(t *testing.T) {
var c calcu
c = add
t.Log(c(12, 45))
}
四、函数作为参数
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
// 函数最为参数,可以直接上面定义的函数类型
func sum(a, b int, c calcu) int {
return c(a, b)
}
// 也可以使用下面这种,这两个使用其实是一样的
func sum1(a, b int, c func(int, int) int) int {
return c(a, b)
}
func TestFuncTwo(t *testing.T) {
res1 := sum(1, 2, add)
res2 := sum1(1, 2, add)
t.Log(res1)
t.Log(res2)
}
五、函数作为返回值
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
func selectOption(s string) (calcu, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("未知函数")
return nil, err
}
}
func TestFuncThree(t *testing.T) {
option, err := selectOption("/")
if err != nil {
panic(err)
}
t.Log(option(1, 2))
}
六、匿名函数
所谓匿名函数,就是没有名字的函数
6.1. 匿名函数的使用方式:
func TestFuncFour(t *testing.T) {
    // 在定义匿名函数的时候就可以直接使用(这种方式只使用一次)
    func1 := func(a, b int) int {
        return  a + b
    } (1, 2)
    t.Log(func1)
    
    // 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
    add := func(a, b int) int {
        return a + b
    }
    func2 := add(1, 2)
    t.Log(func2)
}6.2 全局匿名函数
全局匿名函数就是将匿名函数赋给一个全局变量,那么这个匿名函数在当前程序里可以使用
var (
// Add 就是定义好的全局变量
// 全局变量必须首字母大写
Add = func(a, b int) int {
return a + b
}
)
func main() {
fmt.Println(add(1, 3))
}
七、函数闭包
闭包是匿名函数与匿名函数所引用环境的组合。匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。
7.1. 闭包作为函数返回值
func Increase() func () int {
  n := 0
  return func() int {
    n++
    return n
  }
}
func TestFuncFive(t *testing.T) {
  out := Increase()
  t.Log(out())
  t.Log(out())
  t.Log(out())
}7.2. 返回多个内部函数
func calc1(a int) (func(int) int, func(int) int) {
  add := func(i int) int {
    a += i
    return a
  }
  sub := func(i int) int {
    a -= i
    return a
  }
  return add, sub
}
func TestSix(t *testing.T) {
  f1, f2 := calc1(100)
  t.Log(f1(1), f2(2))  // 101 99
  t.Log(f1(1), f2(2))  // 100 98
  t.Log(f1(2), f2(3))  // 100 97
}其他的使用在golang的并发中在介绍吧
八、延迟函数defer
Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
defer 通常用于简化函数的各种各样清理动作,例如关闭文件,解锁等等的释放资源的动作。
func TestSeven(t *testing.T) {
  defer fmt.Println("No.0")
  fmt.Println("start")
  defer fmt.Println("No.1")
  defer fmt.Println("No.2")
  fmt.Println("end")
  defer fmt.Println("No.3")
}
// 输入结果
start
end
No.3
No.2
No.1
No.0如果返回值是无名的, func test() int {} 则go会在return 的时候创建一个临时的变量s保存return值的动作,之后的defer函数不能操作临时变量s ,只能操作变量i。
func test() int {
    var i int
    defer func() {
        i++
        fmt.Println("defer1 = >", i)
    }()
    defer func() {
        i++
        fmt.Println("defer2 = >", i)
    }()
    return i
}
func TestFuncTen(t *testing.T) {
    t.Log(test())
}
// 输出结果
defer2 = > 1
defer1 = > 2
    func_test.go:162: 0如果返回值是有名的(eg : func test() (i int ) {}) 那么在执行return的时候,就不会创造临时变量去保存i,之后的defer函数可以操作i。
func test1() (i int) {
    defer func() {
        i++
        fmt.Println("defer1 = >", i)
    }()
    defer func() {
        i++
        fmt.Println("defer2 = >", i)
    }()
    return i
}
func TestFuncTen(t *testing.T) {
    t.Log(test1())
}
// 输出结果
defer2 = > 1
defer1 = > 2
    func_test.go:164: 2在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。
 另外 defer 函数会先于 panic 之前执行。
func TestDefer(t *testing.T) {
    defer func() {
        t.Log("Clear resources")
    }()
    t.Log("Started")
    panic("Fail error")
}
结果:
=== RUN   TestDefer
    func_test.go:22: Started
    func_test.go:20: Clear resources
--- FAIL: TestDefer (0.00s)
panic: Fail error [recovered]
    panic: Fail error九、内置函数
| 名称 | 作用 | 
| close | 用来关闭channel | 
| len | 用来求长度 | 
| cap | 用来返回某一个类型最大容量(只用于切片和map) | 
| new | 用来分配内存,主要用来分配值类型,例如int,struct,返回的是指针 | 
| make | 用来分配内存,主要用来分配应用类型,例如:chan, map, slice | 
| copy | 用来复制元素 | 
| append | 用来追加元素到数组,slice中 | 
| panic/recover | 用于错误处理机制 | 
| print/println | 底层打印函数,在项目中建议使用fmt包 | 
| complex/real imag | 用于操作和处理复数 | 
十、 panic/recover
这里需要强调下:panic可以在任何地方引发,但recover只有在defer调用的函数中有效。
10.1 panic
panic 是内建的停止控制流的函数。相当于其他编程语言的抛异常操作。当函数F调用了 panic ,F的执行会被停止,在F中 panic 前面定义的 defer 操作都会被执行,然后F函数返回。对于调用者来说,调用F的行为就像调用 panic (如果F函数内部没有把 panic recover 掉)。如果都没有捕获该 panic ,相当于一层层 panic ,程序将会 crash 。 panic 可以直接调用,也可以是程序运行时错误导致,例如数组越界。
10.2 recover
recover 是一个从 panic 恢复的内建函数。 recover 只有在 defer 的函数里面才能发挥真正的作用。如果是正常的情况(没有发生 panic ),调用 recover 将会返回 nil 并且没有任何影响。如果当前的 goroutine panic 了, recover 的调用将会捕获到 panic 的值,并且恢复正常执行。
func funcA() {
    fmt.Println("func A")
}
func funcB() {
    defer func() {
        err := recover()
        //如果程序出出现了panic错误,可以通过recover恢复过来
        if err != nil {
            fmt.Println("recover in B")
        }
    }()
    panic("panic in B")
}
func funcC() {
    fmt.Println("func C")
}
func main() {
    funcA()
    funcB()
    funcC()
}
// 输出
func A
recover in B
func C使用注意: recover()  必须搭配 defer  使用; defer  一定要在可能引发 panic  的语句前面定义。
                










