三分鐘學 Go 語言——函式深度解析(中)
三分鐘學 Go 語言——函式深度解析(中)
上回函式深度解析給大家聊了一些函式的基本知識,不知道還有沒有人記得,不記得趕緊回去複習!
他們是
go
語言中函式的基本原理- 單/多個同/不同型別引數
- 單/多個同/不同型別返回值
- 值傳遞,引用傳遞
- 函式進階,把函式當作變數傳遞(在不改變函式內部結構的情況下傳入新的實現)
B 站直播分享 go 語言開發入門
明天晚上小熊把咱們技術小組分享搬到了B
站上,可謂是歷史性的大突破!!雖然講的有點磕磕絆絆的,但是有小姐姐誇我聲音渾厚好聽!!為了這些我整整激動了30
分鐘。
我今天這篇文章也是在直播狀態下寫的!
匿名函式
話不多說,今天小熊就帶各位家人感受下go
語言函式中的高階語法。
在前面的文章裡我們學會了把函式當作變數傳遞,可以在不改動原有函式內部實現的情況下,改變函式實現細節(設計模式:裝飾器)。
這種情況下的作為變數傳遞的函式往往只有這一個地方用到了,其他地方不會重複使用。那就沒必要單獨定義一個函式在外面!(多此一舉的事本熊不做!)
like this:
func functionValue(a, b int, do func(int, int) int) { fmt.Println(do(a, b)) } //使用匿名函式的方法呼叫他 實現匿名加函式 funcationValue(1,2,func(a,b int) int{ return a+b }) //使用匿名函式的方法呼叫他 實現匿名減函式 funcationValue(1,2,func(a,b int) int{ return a-b })
在呼叫的時候我們才實現了一個匿名函式(沒有名字的函式)
那是不是隻有把函式當變數傳遞的時候才用到匿名函式呢?並,不,是!
各位同學,讓我上黑板給大家實現一個簡單的匿名函式用法。
f := func(i int) {
fmt.Println(i)
}
f(1)
把匿名函式賦值給一個變數(這裡是f
),f
就是他的函式名,後面就可以直接呼叫啦~,但是這種簡單使用的情況實際上會不會用到呢?很殘酷,幾乎沒有。
匿名函式配合下面的場景使用效果更佳。
閉包
你有沒有一種情況,常常要定義好多全域性變數來共享資料,這種變數一旦多了非常難看,還會汙染環境,有沒有一種辦法,可以通過重複呼叫同一個函式,來修改函式內部的變數呢?
我翻來覆去發現是真的有!這個東西就叫閉包!
閉包的簡單實現,把函式定義在函式內部,並當作返回值返回。
func closureSample() func() {
count := 0
return func() {
count ++
fmt.Printf("呼叫次數 %v \n", count)
}
}
怎麼用才爽?我先喪心病狂的呼叫兩次closureSample
函式,得到兩個函式c1
、c2
,這兩個函式就是closureSample
函式的返回值,型別是一個匿名函式。
c1, c2 := closureSample(), closureSample()
瘋狂呼叫!!!
c1()
c1()
c1()
// 你會發現c2又從1開始輸出,因為兩個函式的變數是獨立使用的
c2()
c2()
輸出
呼叫次數 1
呼叫次數 2
呼叫次數 3
呼叫次數 1
呼叫次數 2
呼叫次數 3
神奇不神奇!在呼叫c2
的時候,完全沒有影響到c1
!
這是因為各個函式是獨立使用一套自己的內部變數,二手賣號地圖互相不影響,所以閉包也可以當測試用例使用。
用來傳入不同的實現,重複呼叫得到不同的返回,不用定義全域性變數。
- 好處:可以減少全域性變數防止變數汙染
- 壞處:延長了區域性變數和函式的生命週期,增加了 gc 的壓力
閉包形式 2
通過上面的例子,不難發現閉包內部的匿名函式可以使用到外部的變數。
閉包形式 2,立即執行函式,宣告完以後加括號,用以表示即刻呼叫。
func() {
// to do something
}()
閉包存在的 bug
go
裡建立一個協程(類似於子執行緒)非常的容易,只要在語句前加一個go
關鍵字就可以了。看看下面這個函式會出現什麼問題。
for i := 0; i < 3; i++ {
fmt.Printf("第一次 i 產生變化中 %v \n", i)
go func() {
fmt.Printf("第一次輸出: %v\n", i)
}()
}
time.Sleep(time.Second)
協程建立完以後立即會執行,但是協程建立這個事件和協程執行程式碼是分離的,他可以全部建立完再執行,而且主執行緒和協程是同時執行的(併發),有可能主執行緒執行完了,協程還沒執行。
這個時候協程才會呼叫外部的變數,i 已經變成 3 了。
第一次 i 產生變化中 0
第一次 i 產生變化中 1
第一次 i 產生變化中 2
第一次輸出: 3
第一次輸出: 3
第一次輸出: 3
解決辦法,建立副本,可以給匿名函式加一個引數,傳值過來自動生成副本
for i := 0; i < 3; i++ {
fmt.Printf("第二次 i 產生變化中 %v \n", i)
go func(tmp int) {
fmt.Printf("第二次輸出: %v\n", tmp)
}(i)
}
time.Sleep(time.Second)
輸出
第二次 i 產生變化中 0
第二次 i 產生變化中 1
第二次輸出: 0
第二次 i 產生變化中 2
第二次輸出: 2
第二次輸出: 1
第二種建立副本的形式
for i := 0; i < 3; i++ {
fmt.Printf("第三次 i 產生變化中 %v \n", i)
tmp := i
go func() {
fmt.Printf("第三次輸出: %v\n", tmp)
}()
}
time.Sleep(time.Second)
輸出
第三次 i 產生變化中 0
第三次 i 產生變化中 1
第三次 i 產生變化中 2
第三次輸出: 0
第三次輸出: 2
第三次輸出: 1
歡迎來 b 站看我每天晚上 學習直播哦~!