1. 程式人生 > 其它 >三分鐘學 Go 語言——函式深度解析(中)

三分鐘學 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函式,得到兩個函式c1c2,這兩個函式就是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 站看我每天晚上 學習直播哦~!