十、Go语法基础(函数)

29 阅读4分钟

Golang中文学习文档地址

1、函数声明

  • 函数声明格式
func 函数名([参数列表]) [返回值] {
  函数体
}
  • 声明函数的两种方式

    • 全局声明函数
    func sum(a, b int) int {
      return a + b
    }
    
    • 将函数作为变量值
    func main() {
        var sum = func(a, b int) int {
            return a + b
        }
        fmt.Println(sum(1, 3))
    }
    
  • 注意:

    在Go中函数没有重载,如果两个函数名相同,入参返回值不一样,会编译不通过。

    因为在Go的理念中,如果两个函数参数不同,则应该为两个不同的函数,所以函数名不应该取一样的。

2、函数参数

  • Go的参数可以不带名称,不过一般是在接口或函数类型声明的时候用,为了可读性,参数最好还是带上名称。
//声明函数类型
type ExWriter func(io.Writer) error
//声明接口
type Writer interface {
  ExWrite([]byte) (int, error)
}
  • 相同参数类型:参数类型相同,可只简写一个类型。
func sum(a,b int) int {//a,b参数类型相同,所以可只写一个int
	return a + b
}
  • 变长参数:可以接收多个参数,但必须声明在最后的参数
func sum(a, b int, other ...int) int {//other为变长参数
	for i, v := range other {
		fmt.Println(i, v)
	}
	return a + b
}
  • 注意

    Go 中的函数参数是传值传递,即在传递参数时会拷贝实参的值。

    切片或Map数据结构本质上都是指针,所以传递时不会复制大量的内存。

3、返回值

  • 无返回值:Go中参数无返回值没有void或其他关键字,只要不写返回值类型就行
func sum(a, b int) {
	fmt.Println(a + b)
}
  • 一个返回值
func sum(a, b int) int { //括号外面的int就是返回值类型
	return a + b
}
  • 多个返回值:Go的特点之一,函数允许有多个返回值
func main() {
	value, str := sum(1, 3)//接收多个返回值
	fmt.Println(value, str)
}
//两个返回值,一个int,一个string
func sum(a, b int) (int, string) {
	return a + b, "hello"
}
  • 具名返回:Go中可以给返回值先取名,然后在后续的return中,无需指定返回哪些值,默认返回取名的返回值。
        func sum(a, b int) (value int, str string) {
                value = a + b
                str = fmt.Sprintf("%d", value)
                return //未指定返回的值,函数默认把value和str返回
        }
    
    • 如果return指定了返回的值,则函数有优先返回return指定的值。
      func sum(a, b int) (value int, str string) {
          value = a + b
          str = fmt.Sprintf("%d", value)
          return 10, "hello" //return指定了其他的值,函数就不会返回value和str的值
     }
    

4、匿名函数

  • 声明函数时没有函数名称,所以称为匿名函数,因没有函数名称,所以只能在函数声明时调用该函数

    func main() {
        value := func(a, b int) int {
            return a + b
        }(1, 2)//直接调用了该匿名函数
        fmt.Println(value)
    }
    
  • 最常的使用场景:当A函数作为B函数的参数时(Go中函数可以作为参数),B被调用,传入函数A时,传入的函数名称就不需要了。

    import "fmt"
    
    func main() {
        value := add(2, func(c, d int) int {//匿名函数作为参数传入函数中。
            return c + d
        })
        fmt.Println(value)
    }
    
    func add(a int, b func(c, d int) int) int { //b参数是一个函数
        return b(a, 2)
    }
    

5、闭包

  • 闭包例子
    import "fmt"
    
    func main() {
            grow := Exp(2) //此时的grow是Exp返回的函数
            for i := range 10 {
                    value := grow() //调用grow时,会使用到Exp函数定义的e变量
                    fmt.Printf("2^%d=%d\n", i, value)
            }
    }
    func Exp(n int) func() int {
            e := 1
            return func() int {
                    temp := e
                    e *= n
                    return temp
            }
    }
    
    • 在grow赋值时,Exp函数的作用域内定义了e变量;当grow赋值结束后,正常e变量应该没用了,会释放内存,但Exp返回的函数,使用的了e变量,相当于被grow使用了,所以e变量无法释放内存(逃逸到堆上),Exp函数声明周期已经结束,到e变量和n变量还没结束(被grow使用),所以grow函数是一个闭包函数。

6、延迟调用

  • 延迟调用使用defer关键字,用defer关键字标识的函数,会在延迟到最后依序执行。
import "fmt"

func main() {
	fmt.Println(1)
	defer deferFunc()
	fmt.Println(2)
}
func deferFunc() {
	fmt.Println(3)
}
  • defer底层的内存结构是一个栈,如果有定义多个defer执行逻辑,先定义的后执行。
func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	defer fmt.Println(4)
	defer fmt.Println(5)
	fmt.Println("start")
}
// 执行结果
start
5
4
3
2
1
  • defer参数预处理

    import "fmt"
    
    func main() {
            fmt.Println("start")
            defer multiplication(sum(1, 2), 3)
            fmt.Println("end")
    }
    func sum(a, b int) int {
            fmt.Println("sum execute。。。。。。")
            return a + b
    }
    func multiplication(c, d int) int {
            fmt.Println("multiplication execute。。。。。。")
            return c * d
    }
    
    • defer的执行逻辑,预期的执行结果应该为:
    start
    end
    sum execute。。。。。。
    multiplication execute。。。。。。
    
    • 实际的执行结果为:
    start
    sum execute。。。。。。
    end
    multiplication execute。。。。。。
    
    • 原因解析:

      defer会对直接作用的函数参数进行预处理,也就是说,defer定义时,保证直接作用的函数参数就是当时的参数值,后续的参数值的更改不会影响defer函数的执行。

      import "fmt"
      
      func main() {
              fmt.Println("start")
              a, b := 1, 2
              defer sum(a, b)//在栈中保存不只函数,还有当前的参数值
              a = 3
              b = 4
              sum(a, b)
      }
      func sum(a, b int)  {
              fmt.Println("sum param:", a, b)
      }
      //执行结果
      start
      sum param: 3 4
      sum param: 1 2