天天看點

Go 函數 函數類型和作為函數傳回值

Go 函數 函數類型和作為函數傳回值

函數也是一種類型,定義變量用var,定義常量用const,定義函數用func。

匿名函數

Go 函數 函數類型和作為函數傳回值

匿名函數,沒有名字,可以在定義的時候就直接運作了。

什麼場景下會使用匿名函數呢?也就是閉包。一個最常用的場景就是程式運作出現錯誤的時候,我們要去恢複,一般是通過關鍵字defer,我們要有一個程式邏輯,在某些特定場景下運作的,但是我沒有必要為整個邏輯定義函數,通常這樣就可以使用閉包來做。

閉包

閉包(Closure)是引用了自由變量的函數,自由變量将和函數一同存在,即使已經離開了創造它的環境,閉包複制的是原對象的指針。

通常情況下一個函數的内部變量,在函數退出之後,這個函數的局部變量都會标志以廢棄,都會被垃圾回收機制給回收,但是閉包就不一樣。

package main

import "fmt"

//閉包(Closure)是引用了自由變量的函數。自由變量将和函數一同存在,即使已經離開了創造它的環境。
func sub() func() {
  i := 10
  fmt.Printf("%p\n", &i)
  b := func() {
    fmt.Printf("i addr %p\n", &i) //閉包複制的是原對象的指針
    i--                           //b函數内部引用了變量i
    fmt.Println(i)
  }
  return b //傳回了b函數,變量i和b函數将一起存在,即使已經離開函數sub()
}

// 外部引用函數參數局部變量
func add(base int) func(int) int {
  return func(i int) int {
    fmt.Printf("base addr %p\n", &base)
    base += i
    return base
  }
}

func main() {
  b := sub()
  b()
  b()
  fmt.Println()

  tmp1 := add(10)
  fmt.Println(tmp1(1), tmp1(2)) //11,13
  // 此時tmp1和tmp2不是一個實體了
  tmp2 := add(100)
  fmt.Println(tmp2(1), tmp2(2)) //101,103
}      

函數也是一種資料類型

func function_arg1(f func(a, b int) int, b int) int { //f參數是一種函數類型
    a := 2 * b
    return f(a, b)
}

type foo func(a, b int) int //foo是一種函數類型
func function_arg2(f foo, b int) int { //參數類型看上去簡潔多了
    a := 2 * b
    return f(a, b)
}



type User struct {
    Name string
    bye foo //bye的類型是foo,而foo代表一種函數類型
    hello func(name string) string //使用匿名函數來聲明struct字段的類型
}

ch := make(chan func(string) string, 10)
ch <- func(name string) string {  //使用匿名函數
    return "hello " + name
}      

函數類型

函數也可以指派給變量,存儲在數組、切片、映射中,也可作為參數傳遞給函數或作為函數傳回值進行傳回。

func add(a,b int) int{
   return a + b
}

func main()  {
   c := add
   fmt.Printf("%v",reflect.TypeOf(c))
}      

這個c到底是什麼東西?可以列印一下c的類型。

func(int, int) int  通過這個也可以看出來函數也是一種類型即函數類型,函數類型由參數類型和數量以及傳回值數量和類型組成的。

如果我想要定義一個函數類型的變量怎麼定義呢?

var f func(int,int) int      

這樣就定義了一個函數類型,變量後面就是函數類型。

var f func(int,int) int
f =add
fmt.Println(f(2,3))      
func add(a,b int) int{
   return a + b
}


func mul(a,b int) int{
   return a - b
}


func main()  {
   c := add
   fmt.Printf("%v",reflect.TypeOf(c))

   fs := []func(int,int) int{c,mul}
    fmt.Println(fs)

   for _,v := range fs{
      fmt.Println(v(2,1))
   }

}      

可以看到函數也是一種類型。函數類型是一個可以将函數類型指派給一個變量。

通過函數類型也可以定義集合類型,如切片,map。

在 Go 語言中,函數類型、map 類型自身,以及切片隻支援與 nil 的比較,而不支援同類型兩個變量的比較。

s1 := make([]int, 1)
s2 := make([]int, 2)

f1 := func() {}
f2 := func() {}

m1 := make(map[int]string)
m2 := make(map[int]string)
println(s1 == s2) // 錯誤:invalid operation: s1 == s2 (slice can only be compared to nil)
println(f1 == f2) // 錯誤:invalid operation: f1 == f2 (func can only be compared to nil)
println(m1 == m2) // 錯誤:invalid operation: m1 == m2 (map can only be compared to nil)      

回調函數(Callback)

Go 函數 函數類型和作為函數傳回值

聲明&調用參數類型為函數的函數

func print1(fmt1 func(string)string,args ...string){
  for i,v := range args{
    fmt.Println(i,fmt1(v))
  }
}

func format(params string)string{
  return "*" + params + "*"
}

func table(params string)string{
  return "|" + params + "|"
}

func main()  {
  names := []string{"jack","lucas"}
  print1(format,names...)
  print1(table,names...)
}      

定義的函數format可以作為參數傳入到print1函數裡面去。print1函數可以接收任意函數類型為func(string)string的函數。上面就是通過函數類将一個函數傳入到另外一個函數裡面去了。

Go 函數 函數類型和作為函數傳回值

 函數類型在定義的時候,一定要定義出它的參數和傳回值。

函數傳回值為函數類型

func()是一個是沒有參數,沒有傳回值的一個函數

func sayHello()  {
    fmt.Println("hello")
}

func sayHi()  {
    fmt.Println("hi")
}

func genFunc() func(){
    rand.Seed(time.Now().Unix())
    if rand.Int() % 2 == 0 {
        return sayHello
    }else {
        return sayHi
    }

}

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

是以函數可以放到切片裡面,map裡面,可以指派給一個變量,可以作為參數傳遞,也可以作為函數值進行傳回。

func aFileds(split rune) bool {
  return split == 'a'
}


  c := strings.FieldsFunc("abcd",aFileds)
  fmt.Println(c)