Go 编程基础 func 08

function

  • Go函数不支持嵌套 重载和默认参数
  • 但是支持一下特性
    • 无需声明原型 不定长变参 多返回值 命名返回值参数 匿名函数 闭包
  • 定义函数使用关键字func,且左大括号不能另起一行
  • 函数也可以作为一种类型使用

函数的多种方式

最简单声明一个func

func A(a int, b string) {

}

多个参数类型一致简写方式

func B(a, b, c int) {

}

返回值不指定

//这里返回值并没有申明有那些返回值
func C() (int, int, int) {
    a, b, c := 1, 2, 3
    //因为前面没有说明返回那些值,所以这里必须要写上a,b,c
    return a, b, c
}

指定返回值

//反之 这里申明了返回值是a b c
func D() (a, b, c int) {
    //所以这里只需要 = 就行了 不能使用 :=
    //因为在这个函数最开始 abc就已经存在了 是局部变量,所以不需要声明变量了
    a, b, c = 4, 5, 6
    //因为申明了返回值是abc 所以直接return
    return
}

不定长变参

//不定长变参一定是在参数最后一个
func E(a string, b ...int){
    fmt.Println(a)
    //不定长变参接收到的是一个slice
    fmt.Println(b)
}

func参数是slice的话是拷贝的是内存地址

func main() {
    //定义slice
    s1 := []int{1, 2, 3, 4}
    //打印未调用函数之前的s1
    fmt.Println(s1)
    //调用函数
    F(s1)
    fmt.Println(s1)
}

func F(s []int) {
    s[0] = 9
    fmt.Println(s)
}

确实影响到了main函数中的s1变量值,说明不是值拷贝.而是内存地址拷贝

[1 2 3 4]
[9 2 3 4]
[9 2 3 4]

如果不是slice的话就只是单纯的值拷贝

func main() {
    a := 2
    F(a)
    fmt.Println(a)
}

func F(s int) {
    s = 9
    fmt.Println(s)
}
9
2

int|string 调用函数也使用内存地址拷贝

func main() {
    a := 2
    //把内存地址传递过去
    F(&a)
    fmt.Println(a)
}
//func 需要加``*``取出内存地址的值,后面的所有的都需要加 ``*``来取得内存地址的值
func F(s *int) {
    *s = 9
    fmt.Println(*s)
}

回调函数

package main

import "fmt"

//先定义函数类型
type OperationFun func(int, int) int

//定义计算器 可以做加减乘除运算
func Calc(a int, b int, Fun OperationFun) int {
    //如果你这是参数是回调函数,你大可不必去实现 OperationFun 的具体方法
    return Fun(a, b)
}

//定义加法运算
func Add2(a int, b int) int {
    return a + b
}

//定义减法运算
func Sub(a int, b int) int {
    return a - b
}

func main() {
    fmt.Print(Calc(1, 4, Sub))
}

函数也可以作为一种类型使用

func main() {
    a := G
    a()
}

func G()  {
    fmt.Println("fun G")
}
fun G

此时看到也打印出了fun G,所以在Go中一切皆类型

匿名函数

func main() {
    a := func() {
        fmt.Println("我是匿名函数")
    }
    a()
}
我是匿名函数

匿名函数的多种方式

package main

import "fmt"

func main() {
    //匿名函数是可以使用函数外的参数的
    a, b := 1, 2

    c := func() {
        fmt.Printf("a=%d,b=%d", a, b)
    }
    c()

    //没参数没返回的匿名函数,不直接调用
    c1 := func() {
        fmt.Print("没参数没返回的匿名函数,不直接调用")
    }
    c1()

    //没参数没返回的匿名函数,直接调用
    func() {
        fmt.Print("没参数没返回的匿名函数,直接调用")
    }()

    //有参数且直接调用的匿名函数
    func(a int, b int) {
        fmt.Printf("a=%d,b=%d", a, b)
    }(1, 2)

    //有参数且直接调用的匿名函数
    min, max := func(a int, b int) (min, max int) {
        if a > b {
            min = b
            max = a
        } else {
            min = a
            max = b
        }
        return
    }(1, 2)
    fmt.Printf("min=%d, max = %d", min, max)
}

匿名函数会修改外部变量的值

func main() {
    a := 1
    b := "mike"
    func() {
        a = 2
        b = "hello"
        fmt.Printf("内部 : a=%d,b=%s\n", a, b)
    }()
    fmt.Printf("外部 : a=%d,b=%s\n", a, b)
}
内部 : a=2,b=hello
外部 : a=2,b=hello

如果闭包内有变量的问题

这种情况是在闭包内初始化了 a 和 b 因为,闭包内有a和b所以就用闭包内的a b,如果没有就会往上一级找

func main() {
    a := 1
    b := "mike"
    func() {
        a := 2
        b := "hello"
        fmt.Printf("内部 : a=%d,b=%s\n", a, b)
    }()
    fmt.Printf("外部 : a=%d,b=%s\n", a, b)
}
内部 : a=2,b=hello
外部 : a=1,b=mike

返回值是一个匿名函数

func fun1() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}

func main() {
    //返回值为一个匿名函数,反正一个函数类型,通过f来调用返回的匿名函数,f来调用闭包函数
    //它不关心这些捕获了的变量和常量是否已经超出了作用域
    //所以只有闭包还在使用它,这些变量就还会存在
    f := fun1() // x=0
    fmt.Println(f())// x= 1 结果:1
    fmt.Println(f())// x= 2 结果:4
    fmt.Println(f())// x= 3 结果:9
    fmt.Println(f())// x= 4 结果:16
    fmt.Println(f())// x= 5 结果:25
}
1
4
9
16
25

闭包

func main() {
    a := closure(10)
    fmt.Println(a(1))
    fmt.Println(a(2))
}

func closure(x int) func(int) int {
    fmt.Printf("%p\n", &x)
    return func(y int) int {
        fmt.Printf("%p\n", &x)
        //这里的x是从外层函数得到的x
        return x + y
    }
}
0xc420016090
0xc420016090
11
0xc420016090
12
  • 最终得到了11 和 12 说明答案是正确的
  • 这里一共打印出了三次x的内存地址,且都一样.说明确实是一个闭包

defer

  • defer的执行顺序类似与其他语言中的析构函数, 在函数体执行结束后按照调用顺序的相反顺序逐个执行(类似于栈的储存方式,先进后出,后进先出)
  • 即使函数发生严重错误也会执行
  • 支持匿名函数的调用
  • 常用于资源清理 文件关闭 解锁 已经记录时间等操作
  • 通过与几名函数配合可在reuturn之后修改函数计算结果
  • 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer是即已经获得了拷贝,否则则是引起某个变量的地址
  • Go没有异常机制 但有 panic/recover模式来处理错误
  • Panic可以在任何地方引发, 但recover只有在defer调用的函数中有效

defer执行顺序的例子

func main() {
    fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")
}

因为是按照定义顺序相反执行的,所以先打印c 再 打印b

a
c
b

defer循环的例子

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
2
1
0

defer结合匿名函数

func main() {
    for i := 0; i < 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}
3
3
3

这里呢是一个闭包,闭包作为一个局部变量,在一直引用i 循环的时候并不会执行defer,而是等到循环完毕 main函数退出的时候 才执行defer 这时候i已经是3了 所以打印出3个3

defer只有在初始化了才会被执行

func main() {
    defer fmt.Println("aaaaaaaaaa")
    defer fmt.Println("bbbbbbbbb")
    test(0)
    defer fmt.Println("ccccccc")
}
//这里写一个会引发panic的方法
func test(i int) int {
    return 100 / i
}
bbbbbbbbb
aaaaaaaaaa
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.test(...)
    /Users/ailuoy/.gvm/pkgsets/go1.10.2/global/src/czbkgo/day2/多个defer的执行顺序.go:13
main.main()
    /Users/ailuoy/.gvm/pkgsets/go1.10.2/global/src/czbkgo/day2/多个defer的执行顺序.go:8 +0xd6
exit status 2

很奇怪 为什么没有打印出cccc呢

因为进入main函数,先初始化了aa bbb的两个defer,然后调用函数. 这时候发生错误,退出main函数 此时因为有defer所以就执行了bbb aaa的defer(因为ccc的defer压根就没有被初始化)

defer结合匿名函数使用

func main() {
    a, b := 10, 20
    defer func() {
        fmt.Printf("内部 a=%d,b=%d\n", a, b)
    }()
    a, b = 30, 40
    fmt.Printf("外部 a=%d,b=%d\n", a, b)
}
内部 a=30,b=40
外部 a=30,b=40

defer结合匿名函数使用2

func main() {
    a, b := 10, 20
    defer func(a, b int) {
        fmt.Printf("内部 a=%d,b=%d\n", a, b)
    }(a, b)
    a, b = 30, 40
    fmt.Printf("外部 a=%d,b=%d\n", a, b)
}
外部 a=30,b=40
内部 a=10,b=20

因为这里已经传参了.. 内部已经有参数了

panic和recover的使用

func main() {
    A()
    B()
    C()
}

func A() {
    fmt.Println("我是函数A")
}

func B() {
    panic("我这里出错了,显示不出来,我后面的都不会执行")
}

func C() {
    fmt.Println("我是函数C")
}
我是函数A
panic: 我这里出错了,显示不出来,我后面的都不会执行

goroutine 1 [running]:
main.B()
    /Users/ailuoy/.gvm/pkgsets/go1.10.2/global/src/practice/basic_structure.go:32 +0x39
main.main()
    /Users/ailuoy/.gvm/pkgsets/go1.10.2/global/src/practice/basic_structure.go:23 +0x25

这里确实也是这样,因为调用B的时候panic了所以程序终止了

使用recover

需要注意的是 defer 一定要写到panic之前,这样提前注册recover这样当执行到panic的时候才会触发recover

func main() {
    A()
    B()
    C()
}

func A() {
    fmt.Println("我是函数A")
}

func B() {
    defer func() {
        //recover()返回panic的信息
        if err := recover(); err != nil {
            fmt.Println("我已经recover了...")
        }
    }()
    panic("我这里出错了,显示不出来,我后面的都不会执行")
}

func C() {
    fmt.Println("我是函数C")
}
我是函数A
我已经recover了...
我是函数C