這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
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