函数定义

Go语言中定义函数使用func关键字,具体格式如下:

  1. func 函数名(参数)(返回值){
  2. 函数体
  3. }

函数参数

  • 类型简写
    函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
  1. func intSum(x, y int) int {
  2. return x + y
  3. }
  • 可变参数
    和java一样,Go语言中的可变参数通过在参数名后加…来标识。
  1. func intSum2(x ...int) int {
  2. fmt.Println(x)
  3. sum := 0
  4. for _, v := range x {
  5. sum = sum + v
  6. }
  7. return sum
  8. }

在函数,可变参数需要当成是一个切片来处理

函数返回值

Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

  1. func calc(x, y int) (int, int) {
  2. sum := x + y
  3. sub := x - y
  4. return sum, sub
  5. }

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

  1. func calc(x, y int) (sum int, sub int) {
  2. sum := x + y
  3. sub := x - y
  4. return
  5. }

这看起来感觉没有什么意义,再看下面代码:

  1. func f() (x int) {
  2. defer func() {
  3. x++
  4. }()
  5. return 5
  6. }

关于defer的用法在后续会讲到,大体作用就是在返回之前调用,这里对返回值进行命名后就能在defer中直接使用,调用f返回值是6。

函数类型和函数变量

使用type关键字来定义一个函数类型

  1. type calculation func(int, int) int

上面语句定义了一个calculation类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。

函数变量可以和普通变量一样,作为函数的参数,返回值,或赋值。如下代码:

  1. func add(x, y int) int {
  2. return x + y
  3. }
  4. var cal calculation
  5. cal = add
  6. cal(1,2) //返回值为3

函数变量类似C中的函数指针,有了函数变量后,就能把函数作为参数传递到函数内部执行。

  • 函数变量作为参数
  1. //定义函数变量
  2. type calculation func(int, int) int
  3. //定义calc函数,其中一个参数是calculation类型
  4. func calc(x, y int, op calculation) int {
  5. return op(x, y)
  6. }
  7. //或者使用匿名函数
  8. func calc(x, y int, op func(int, int) int) int {
  9. return op(x, y)
  10. }
  11. func main() {
  12. ret2 := calc(10, 20, add)
  13. fmt.Println(ret2) //30
  14. }
  • 函数变量作为返回值
  1. //定义函数变量
  2. type calculation func(int, int) int
  3. //加法
  4. func add(x, y int) int {
  5. return x + y
  6. }
  7. //减法
  8. func sub(x, y int) int {
  9. return x - y
  10. }
  11. //根据运算符号返回函数
  12. func getCal(symbol string) calculation {
  13. switch symbol {
  14. case "+":
  15. return add
  16. case "-":
  17. return sub
  18. default:
  19. return nil
  20. }
  21. }
  22. func main() {
  23. result := getCal("+")(1,2)
  24. fmt.Println(result)//3
  25. }

匿名函数

函数内部也可以定义函数,但是只能定义匿名函数,匿名函数格式如下:

  1. func(参数)(返回值){
  2. 函数体
  3. }

我们可以运行匿名函数对上面的getCal函数进行改造

  1. func getCal(symbol string) calculation {
  2. switch symbol {
  3. case "+":
  4. return func(x, y int) int {
  5. return x + y
  6. }
  7. case "-":
  8. return func(x, y int) int {
  9. return x - y
  10. }
  11. default:
  12. return nil
  13. }
  14. }

闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。看如下例子:

  1. //闭包实现累加
  2. func addup() func(int) int {
  3. var sum int
  4. return func(y int) int {
  5. sum += y
  6. return sum
  7. }
  8. }
  9. func main() {
  10. f1 := addup()
  11. fmt.Println(f1(10)) //10
  12. fmt.Println(f1(20)) //30
  13. fmt.Println(f1(30)) //60
  14. f2 := addup()
  15. fmt.Println(f2(40)) //40
  16. fmt.Println(f2(50)) //90
  17. fmt.Println(f2(60)) //150
  18. }

当调用addup()返回f1的时候,f1实际是引用了外部作用域的一个sum变量,此时sum的初始值为0.在f1的生命周期内,变量sum也一直有效。其中f1和f2引用的sum变量是不同的。

再看个例子:

  1. func calc(base int) (func(int) int, func(int) int) {
  2. add := func(i int) int {
  3. base += i
  4. return base
  5. }
  6. sub := func(i int) int {
  7. base -= i
  8. return base
  9. }
  10. return add, sub
  11. }
  12. func main() {
  13. f1, f2 := calc(10)
  14. fmt.Println(f1(1), f2(2)) //11 9
  15. fmt.Println(f1(3), f2(4)) //12 8
  16. fmt.Println(f1(5), f2(6)) //13 7
  17. }

f1和f2都引用了base变量,所以f1和f2的调用是会互相影响的,因为他们使用的是同一个base的值。

defer关键字

go中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的主体函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。例如:

  1. func main() {
  2. fmt.Println("start")
  3. defer fmt.Println(1)
  4. defer fmt.Println(2)
  5. defer fmt.Println(3)
  6. fmt.Println("end")
  7. }
  8. 输出结果为:
  9. start
  10. end
  11. 3
  12. 2
  13. 1

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等

go函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。如下图:
Clipboard_2020-10-12-18-10-15.png

看一个打脑壳的面试题:

  1. func calc(index string, a, b int) int {
  2. ret := a + b
  3. fmt.Println(index, a, b, ret)
  4. return ret
  5. }
  6. func main() {
  7. x := 1
  8. y := 2
  9. defer calc("AA", x, calc("A", x, y))
  10. x = 10
  11. defer calc("BB", x, calc("B", x, y))
  12. y = 20
  13. }

答案我先不贴出来,以后看到可以先思考下。提示一点:defer的作用域

panic和recover实现异常机制

panic可以理解成java中的throw,当执行到panic的时候会使程序异常退出。这个时候就要用recover来捕捉到panic,可以理解成java中的catch。有点需要注意,recover只有在defer调用的函数中有效。
看如下例子

  1. func funcA() {
  2. defer func() {
  3. err := recover()
  4. //如果程序出出现了panic错误,可以通过recover恢复过来
  5. if err != nil {
  6. fmt.Println(err)
  7. fmt.Println("recover in A")
  8. }
  9. }()
  10. panic("panic in A")
  11. }
  12. func funcB() {
  13. fmt.Println("func B")
  14. }
  15. func main() {
  16. funcA()
  17. funcB()
  18. }
  19. 输出结果为:
  20. panic in A
  21. recover in A
  22. func B

函数init

每个包可以包含任意多个init 函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init 函数都会安排在main 函数之前执行。init 函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。