Go中的函數一些有趣的功能


Go中的函數

函數是Go里面的核心設計,它通過關鍵字func來申明,他的格式如下
func funcname(input1 type1, input2 type2) (output1 type1, output2 type2) {
         //這里是處理邏輯代碼
         //返回多個值
         return value1, value2
}

函數有以下特征:

  • 關鍵字func用來申明一個函數funcname,匿名函數可以沒有funcname。
  • 函數可以有一個或者多個參數,每個參數后面帶有類型,通過,分隔
  • 函數可以返回多個值
  • 上面返回值申明了兩個變量output1和output2,如果你不想申明也可以,直接就兩個類型
  • 如果只有一個返回值且不申明返回值變量,那么你可以省略用以包括返回值的括號
  • 如果沒有返回值,那么就直接省略最后的返回信息

 

函數作為值、類型

在Go中函數也是一種變量,我們可以通過type來定義他,他的類型就是所有擁有相同的參數,相同的返回值的一種類型 type type_name func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])

函數作為類型到底有什么好處呢?那就是可以把這個類型的函數當做值來傳遞,請看下面的例子。

package main
import "fmt"

type test_int func(int) bool //申明了一個函數類型

func isOdd(integer int) bool {
        if integer%2 == 0 {
               return false
       }
       return true
}

func isEven(integer int) bool {
       if integer%2 == 0 {
              return true
       }
       return false
}

//申明的函數類型在這個地方當做了一個參數
func filter(slice []int, f test_int) []int {
       var result []int
       for _, value := range slice {
              if f(value) {
                     result = append(result, value)
              }
       }
       return result
}

func main(){
       slice := []int {1, 2, 3, 4, 5, 7}
       fmt.Println("slice = ", slice)
       odd := filter(slice, isOdd) //函數當做值來傳遞了
       fmt.Println("Odd elements of slice are: ", odd)
       even := filter(slice, isEven)//函數當做值來傳遞了
       fmt.Println("Even elements of slice are: ", even)
}

函數當做值和類型在我們寫一些通用接口的時候非常有用,通過上面例子我們看到test_int這個類型是一個函數類型,然后兩個filter函數的參數和返回值與test_int類型是一樣的,但是我們可以實現很多種的邏輯,這樣使得我們的程序變得非常的靈活。

函數返回值是函數的情況

技術參考: http://www.cnblogs.com/cool-xing/archive/2012/05/19/2509176.html

假如你擁有一份吃過壽司的人的清單, 你是否能夠根據人名確定他是否在清單上? 這是個很簡單的問題, 你只需遍歷清單. 嗯, 如果你go的功底很弱, 不知道怎么遍歷清單那怎么辦? 沒關系, 我會給你提供一個刷選器:

func Screen(patients []string) func(string) bool {
         // 定義匿名函數並返回
         return func(name string) bool {
                  for _, soul := range patients {
                           if soul == name {
                                    return true
                           }
                  }
                  return false
         }
}

Screen方法會將刷選的函數返回給調用方, 這樣你就可以不用懂怎么去遍歷清單了, 你只需調用我返回給你的函數就可以:

// 吃過壽司的人的清單
those_who_bought_sushi := []string{"Anand", "JoJo", "Jin", "Mon", "Peter", "Sachin"}
// 得到刷選器函數
bought_sushi := Screen(those_who_bought_sushi)
// 調用刷選器函數就可以知道某人是否在清單上
fmt.Println(bought_sushi("Anand")) // true
fmt.Println(bought_sushi("Alex")) // false

閉包

地球人都知道:函數只是一段可執行代碼,編譯后就“固化”了,每個函數在內存中只有一份實例,得到函數的入口點便可以執行函數了。go語言中函數可以作為另一個函數的參數或返回值,可以賦給一個變量。函數可以嵌套定義(使用匿名函數),即在一個函數內部可以定義另一個函數,有了嵌套函數這種結構,便會產生閉包問題。如:

package main
import "fmt"

func ExFunc(n int) func() {
         sum:=n
         return func () { //把匿名函數作為值賦給變量a (Go 不允許函數嵌套。
                                   //然而你可以利用匿名函數實現函數嵌套)
                  fmt.Println(sum+1) //調用本函數外的變量
         } //這里沒有()匿名函數不會馬上執行
}

func main() {
         myFunc:=ExFunc(10)
         myFunc()     // 11
         myAnotherFunc:=ExFunc(20)
         myAnotherFunc()    //21
         myFunc()       //11
         myAnotherFunc()   //21
}

這里執行結果:

11


21


11


21

在這段程序中,匿名函數是函數ExFunc的內嵌函數,並且是ExFunc函數的返回值。我們注意到一個問題:這里的匿名內嵌函數中引用到外層函數中的局部變量sum,Go會這么處理這個問題呢?先讓我們來看看這段代碼的運行結果。當我們調用分別由不同的參數調用ExFunc函數得到的函數時(myFunc(),myAnotherFunc()),得到的結果是隔離的,也就是說每次調用ExFunc函數后都將生成並保存一個新的局部變量sum。其實這里ExFunc函數返回的就是閉包。

 

按照命令式語言的規則,ExFunc函數只是返回了內嵌函數InsFunc的地址,在執行InsFunc函數時將會由於在其作用域內找不到sum變量而出錯。而在函數式語言中,當內嵌函數體內引用到體外的變量時,將會把定義時涉及到的引用環境和函數體打包成一個整體(閉包)返回。現在給出引用環境的定義就容易理解了:引用環境是指在程序執行中的某個點所有處於活躍狀態的約束(一個變量的名字和其所代表的對象之間的聯系)所組成的集合。閉包的使用和正常的函數調用沒有區別。

 

由於閉包把函數和運行時的引用環境打包成為一個新的整體,所以就解決了函數編程中的嵌套所引發的問題。如上述代碼段中,當每次調用ExFunc函數時都將返回一個新的閉包實例,這些實例之間是隔離的,分別包含調用時不同的引用環境現場。不同於函數,閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。

閉包函數是把創建時,引用到的外部數據復制了一份,與函數一起組成了一個整體。

閉包函數出現的條件:
1.被嵌套的函數引用到非本函數的外部數據,而且這外部數據不是“全局變量”
2.函數被獨立了出來(被父函數返回或賦值給其它函數或變量了)

回來看閉包的定義:閉包是什么,閉包是由函數及其相關的引用環境組合而成的實體(即:閉包=函數+引用環境)。

對象是附有行為的數據,而閉包是附有數據的行為

參考: http://www.cnblogs.com/Jifangliang/archive/2008/08/05/1260602.html

http://blog.sina.com.cn/s/blog_487109d101018fcx.html

package main
 
import "fmt"
 
 
func ExFunc(n int) func() {
    sum := n
    a := func() {
        sum++        //在這里對外部數據加1
        fmt.Println(sum)
    }
    return a
}
 
func main() {
    myFunc := ExFunc(10)
    myFunc()
    myAnotherFunc := ExFunc(20)
    myAnotherFunc()
 
    myFunc()
    myAnotherFunc()     //這里得出的結果是22,由此可以證明兩點
                        //1.閉包中對外部數據的修改,外部不可見
                        //2.外部數據的值被保存到新建的靜態變量中
                    
}
 
 

試驗

看下面幾種情況,對比執行結果

 
 
 
         package main
 
import"fmt"
 
func main(){
    var j int=5
 
    a:=func()func(){
        var i int=10
        fmt.Printf("\neeee:%d\n",j)
        return func(){
            fmt.Printf("i,j:%d,%d\n",i,j)
        }
    }()
 
    a()
    j*=2
    a()
}
執行結果:

eeee:5

i,j:10,5

i,j:10,10

exit code 0, process exited normally.


 
 
 
         

例子二

 
 
 
         package main
 
import"fmt"
 
func main(){
    var j int=5
 
    a:=func()func(){
        var i int=10
        fmt.Printf("\neeee:%d\n",j)
        return func(){
            fmt.Printf("i,j:%d,%d\n",i,j)
        }
    }
 
    a()
    j*=2
    a()
}
執行結果:

eeee:5

eeee:10
exit code 0, process exited normally.

例子三

 
 
 
         package main
 
import"fmt"
 
func main(){
    var j int=5
 
    a:=func() func(){
        var i int=10
        fmt.Printf("\neeee:%d\n",j)
        return func(){
            fmt.Printf("i,j:%d,%d\n",i,j)
        }
    }
 
    a()()
    j*=2
    a()()
}
執行結果:

eeee:5
i,j:10,5

eeee:10
i,j:10,10
exit code 0, process exited normally.

 

 
 
 
         
 
 
 
         

 

參考資料:

https://github.com/astaxie/build-web-application-with-golang/blob/master/02.3.md

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM