Go 函数(Go面试系列)

310次阅读  |  发布于3年以前

今天和大家一起学习Go语言的函数。 函数是Go语言中的一等公民,既可以单独定义,也可以作为参数传递。

Go语言中函数和方法不是一个概念,方法是属于结构体的。这涉及到Go语言面向对象的编程哲学,我们在后面的文章中再讨论。

声明函数

func 函数名(传入参数)  (返回值) {
   函数体
}

用func关键字声明一个函数。传入参数是一个或者多个,返回值也是一个或者多个(这一点和别的语言不太一样,Go语言支持多返回值)。

基本用法

func f1(x int, y int) (int ,int ){
   return x+y,x-y
}

func f2(x int,y int) (int,int ) {
   return x*y,x/y
}

func main()  {
   a,b := f1(3,4)
   c, d := f2(3,4)
   fmt.Printf("a=%d \n",a) // a=7
   fmt.Printf("b=%d \n",b) // b=-1
   fmt.Printf("c=%d \n",c) // c=12
   fmt.Printf("d=%d \n",d) // d=0
}

f1是一个具体的函数,其中x,y是传入参数,定义函数时指定的传入参数称为「形参」a,b := f1(3,4)是对函数f1的调用,其中a,b会顺序接收f1的返回值。3,4是「实参」,Go语言中实参是按值传递的,所以函数接收到的是每个实参的副本。如果实参包含了引用类型比如:指针、slice、map、函数、channel,则函数接收到的是指针地址的副本。

f1和f2两个函数,传入参数类型和顺序是完全相同的,返回值的个数和类型也是完全相同。在Go语言中,传入参数和返回参数完全相同的函数是同一类函数。

Go语言中的类型(type)也是非常重要的概念,我们后续单独写文章介绍,当前只需要记住判断两个函数是否同类型的标准就可以了。

返回值

Go语言中的函数可以有多个返回值,同时我们也可以对返回值进行命名。

func f1(x int, y int) (sum int , differe int ){
   sum = x+y
   differe = x-y
   return
}

func f2(x int, y int) (sum int , differe int ){
   sum = x+y
   differe = x-y
   return x,y
}

func main()  {
   a,b := f1(3,4)
   c,d := f2(3,4)
   fmt.Printf("a=%d \n",a) // print a=7
   fmt.Printf("b=%d \n",b) // print b=-1
   fmt.Printf("c=%d \n",c) // print a=3
   fmt.Printf("d=%d \n",d) // print b=4
}

如示例中的f1,对函数返回值显式命名时,return后面可以省略返回值。如果即给返回值命名,又return了返回值,return后面的返回值会生效,所以f2的返回结果是3,4。

匿名函数

匿名函数就是没有名字的函数,通常有两种使用方式。如下示例:

func main()  {
   a,b := 1,2
   // 方式一 把匿名函数复制给变量
   f1 := func() int{
      return a+b
   }
   fmt.Printf("f1的执行结果:%d \n",f1());

   // 方式二 直接调用
   sum := func() int{
      return a+b
   }()
   fmt.Printf("f2的执行结果:%d \n",sum);
}

可以把匿名函数赋值给一个变量,然后在后面添加()调用,也可以在匿名函数后面直接跟()调用。匿名函数也可以有传入参数。我们再来看一个例子:

func main()  {
   a := 1

   go func(b int) {
      time.Sleep(100*time.Millisecond)
      fmt.Println("a2=", b) // print a2=1
   } (a)

   a++
   fmt.Println("a1=", a) // print a1=2
   time.Sleep(5*time.Second)
}

匿名函数在一个Go rountine中运行,传入了值a,然后外面的变量a++,此时a变成了2,但是协程中的a仍然是1,这是因为匿名函数和普通的函数一样,调用时都会对实参的值进行拷贝。

不定长度函数

如fmt.Println()函数,传入的参数个数是不确定的。我们实现一个输出最大值的函数max(),可以接收到不定长的参数。

import "fmt"

func max(val ...int) int{
   var ret int
   for _,m := range val{
      if ret < m {
         ret = m
      }
   }
   return ret
}

func main()  {
   fmt.Printf("max=%d \n",max(3,4,6,9,0)) // print  9
   fmt.Printf("max=%d \n",max(-1,8,65)) // print 65

   arr := []int{99,23,56,8,12,5}
   fmt.Printf("max=%d \n",max(arr...)) // print 99
}

如上示例,max函数接收int类型的参数不确定个数。Go语言中函数的最后一个形参类型前加...代表不限定参数个数。

高阶函数

当一个函数满足以下两个条件之一时,我们称之为高阶函数:

  1. 接受一个或多个函数作为输入
  2. 返回一个函数

下面我们举两个例子说明下高阶函数如何使用。 例1:实现红绿灯功能,每隔一秒,灯的颜色变换一次。

import (
   "fmt"
   "time"
)

// 高阶函数,传入的handler函数执行红绿灯切换
func highFun(s []string ,handler func(string,time.Duration))  func(){
   var count = 0
   var f func()
   f = func() {
      m := s[count%len(s)] // 通过对count取余,循环获取数组下表
      handler(m,2*time.Second)
      time.Sleep(2*time.Second)
      count++
      f()
   }
   return f
}

func main()  {
   f := func(color string, t time.Duration) {
      fmt.Printf("等待 %v 秒后,%s 灯亮。 \n", t, color)
   }
   light := highFun([]string{"红灯","绿灯","黄灯"},f)
   light()
}

我们定义highFun函数,用来控制等待时间,并触发handler函数控制红绿灯切换;同时把控制函数f,作为返回值返回, 让调用方决定何时调用。

例2:统计数组中各单词出现的频率。

我们首先实现一个类似javascript中的reduce方法,不断对当前函数f()的返回值和数组中的下一个元素调用f()方法;然后通过一个匿名函数,累加相同单词的数量,完成统计功能。

import "fmt"

func reduce(arr []string, f func(string,map[string] int) map[string] int , m map[string] int) func(arr []interface{}, f func(...interface{}) , m map[string] int) {
   if len(arr) >0 {
      return reduce(arr[1:],f,f(arr[0],m))
   }
   return nil
}

func countWord(arr []string)  {
   m := make(map[string] int)
   reduce(arr, func(s string, m map[string]int) map[string]int {
      m[s]++
      return m
   },m)
   fmt.Printf("统计结果:%v \n",m) // print map[bad:2 beautiful:2 good:2] 
   return
}
func main()  {
   var arr = []string{"good","bad","beautiful","bad","good","beautiful"}
   countWord(arr)
}

reduce方法接收原始数组、处理累加逻辑的函数、存储结果的map三个值。reduce迭代执行,每次迭代把数组的第一个值和这一轮的计算结果传入处理函数,并把返回值带入下一轮迭代,直到整个数组遍历完。

总结

本文我们主要介绍了,函数的基本用法,匿名函数、不定长函数和高阶函数。在我们实际编程中,还是使用普通函数的场景较多。对于匿名函数、不定长函数和高阶函数在开发通用模块时往往能起到很好的封装作用。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8