函数定义

函数是组织好的、可重复使用的、用于执行指定任务的代码块
Go语言支持:函数、匿名函数和闭包
Go语言中定义函数使用func关键字,具体格式如下:

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

其中:

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也不能重名

示例

  1. // 求两个数的和
  2. func sumFn(x int, y int) int{
  3. return x + y
  4. }
  5. // 调用方式
  6. sunFn(1, 2)

获取可变的参数,可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后面加… 来标识。
注意:可变参数通常要作为函数的最后一个参数

  1. func sunFn2(x ...int) int {
  2. sum := 0
  3. for _, num := range x {
  4. sum = sum + num
  5. }
  6. return sum
  7. }
  8. // 调用方法
  9. sunFn2(1, 2, 3, 4, 5, 7)

方法多返回值,Go语言中函数支持多返回值,同时还支持返回值命名,函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回

  1. // 方法多返回值
  2. func sunFn4(x int, y int)(sum int, sub int) {
  3. sum = x + y
  4. sub = x -y
  5. return
  6. }

函数类型和变量

定义函数类型

我们可以使用type关键字来定义一个函数类型,具体格式如下

  1. type calculation func(int, int) int

上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。
简单来说,凡是满足这两个条件的函数都是calculation类型的函数,例如下面的add 和 sub 是calculation类型

  1. type calc func(int, int) int
  2. // 求两个数的和
  3. func sumFn(x int, y int) int{
  4. return x + y
  5. }
  6. func main() {
  7. var c calc
  8. c = add
  9. }

方法作为参数

  1. /**
  2. 传递两个参数和一个方法
  3. */
  4. func sunFn (a int, b int, sum func(int, int)int) int {
  5. return sum(a, b)
  6. }

或者使用switch定义方法,这里用到了匿名函数

  1. // 返回一个方法
  2. type calcType func(int, int)int
  3. func do(o string) calcType {
  4. switch o {
  5. case "+":
  6. return func(i int, i2 int) int {
  7. return i + i2
  8. }
  9. case "-":
  10. return func(i int, i2 int) int {
  11. return i - i2
  12. }
  13. case "*":
  14. return func(i int, i2 int) int {
  15. return i * i2
  16. }
  17. case "/":
  18. return func(i int, i2 int) int {
  19. return i / i2
  20. }
  21. default:
  22. return nil
  23. }
  24. }
  25. func main() {
  26. add := do("+")
  27. fmt.Println(add(1,5))
  28. }

匿名函数

函数当然还可以作为返回值,但是在Go语言中,函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下

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

匿名函数因为没有函数名,所以没有办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:

  1. func main() {
  2. func () {
  3. fmt.Println("匿名自执行函数")
  4. }()
  5. }

Golang中的闭包

全局变量和局部变量

全局变量的特点:

  • 常驻内存
  • 污染全局

局部变量的特点

  • 不常驻内存
  • 不污染全局

    闭包

  • 可以让一个变量常驻内存

  • 可以让一个变量不污染全局

闭包可以理解成 “定义在一个函数内部的函数”。在本质上,闭包就是将函数内部 和 函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。

  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • 创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

注意:由于闭包里作用域返回的局部变量资源不会被立刻销毁,所以可能会占用更多的内存,过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

  1. // 闭包的写法:函数里面嵌套一个函数,最后返回里面的函数就形成了闭包
  2. func adder() func() int {
  3. var i = 10
  4. return func() int {
  5. return i + 1
  6. }
  7. }
  8. func main() {
  9. var fn = adder()
  10. fmt.Println(fn())
  11. fmt.Println(fn())
  12. fmt.Println(fn())
  13. }

最后输出的结果

  1. 11
  2. 11
  3. 11

另一个闭包的写法,让一个变量常驻内存,不污染全局

  1. func adder2() func(y int) int {
  2. var i = 10
  3. return func(y int) int {
  4. i = i + y
  5. return i
  6. }
  7. }
  8. func main() {
  9. var fn2 = adder2()
  10. fmt.Println(fn2(10))
  11. fmt.Println(fn2(10))
  12. fmt.Println(fn2(10))
  13. }

defer语句

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

  1. // defer函数
  2. fmt.Println("1")
  3. defer fmt.Println("2")
  4. fmt.Println("3")
  5. fmt.Println("4")

defer将会延迟执行

  1. 1
  2. 3
  3. 4
  4. 2

如果有多个defer修饰的语句,将会逆序进行执行

  1. // defer函数
  2. fmt.Println("1")
  3. defer fmt.Println("2")
  4. defer fmt.Println("3")
  5. fmt.Println("4")

运行结果

  1. 1
  2. 4
  3. 3
  4. 2

如果需要用defer运行一系列的语句,那么就可以使用匿名函数

  1. func main() {
  2. fmt.Println("开始")
  3. defer func() {
  4. fmt.Println("1")
  5. fmt.Println("2")
  6. }()
  7. fmt.Println("结束")
  8. }

运行结果

  1. 开始
  2. 结束
  3. 1
  4. 2

defer执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前,具体如下图所示
Go的函数 - 图1

panic/revocer处理异常

Go语言中是没有异常机制,但是使用panic / recover模式来处理错误

  • panic:可以在任何地方引发
  • recover:只有在defer调用函数内有效
    1. func fn1() {
    2. fmt.Println("fn1")
    3. }
    4. func fn2() {
    5. panic("抛出一个异常")
    6. }
    7. func main() {
    8. fn1()
    9. fn2()
    10. fmt.Println("结束")
    11. }
    上述程序会直接抛出异常,无法正常运行
    1. fn1
    2. panic: 抛出一个异常
    解决方法就是使用 recover进行异常的监听
    1. func fn1() {
    2. fmt.Println("fn1")
    3. }
    4. func fn2() {
    5. // 使用recover监听异常
    6. defer func() {
    7. err := recover()
    8. if err != nil {
    9. fmt.Println(err)
    10. }
    11. }()
    12. panic("抛出一个异常")
    13. }
    14. func main() {
    15. fn1()
    16. fn2()
    17. fmt.Println("结束")
    18. }

    异常运用场景

    模拟一个读取文件的方法,这里可以主动发送使用panic 和 recover
    1. func readFile(fileName string) error {
    2. if fileName == "main.go" {
    3. return nil
    4. } else {
    5. return errors.New("读取文件失败")
    6. }
    7. }
    8. func myFn () {
    9. defer func() {
    10. e := recover()
    11. if e != nil {
    12. fmt.Println("给管理员发送邮件")
    13. }
    14. }()
    15. err := readFile("XXX.go")
    16. if err != nil {
    17. panic(err)
    18. }
    19. }
    20. func main() {
    21. myFn()
    22. }

    总结:

    painc一旦被触发,在panic后面的语句不会被执行。但是如果panic前面有defer的话会进入defer里面,通过recover进行error的获取

    内置函数

    | 内置函数 | 介绍 | | —- | —- | | close | 主要用来关闭channel | | len | 用来求长度,比如string、array、slice、map、channel | | new | 用来分配内存、主要用来分配值类型,比如 int、struct ,返回的是指针 | | make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice | | append | 用来追加元素到数组、slice中 | | panic\recover | 用来处理错误 |