Go語言中的for迴圈有多坑?

2022-11-07 18:03:01
本文由欄目給大家介紹關於Go for 迴圈的面試問題,不知道大家對for迴圈瞭解多少,有沒有覺得很坑?下面就給大家詳細聊聊for相關問題,希望對需要的朋友有所幫助!

不知道有多少 Go 的面試題和洩露,都和 for 迴圈有關。今天我在週末認真一看,發現了 redefining for loop variable semantics 。

著名的硬核大佬 Russ Cox 表示他一直在研究這個問題,並表示十年的經驗表明了當前語意的代價是很大的。

問題

  • 案例一:取地址符

在 Go 語言中,我們寫 for 語句時有時會出現執行和猜想的結果不一致。例如以下第一個案例的程式碼:

var all []*Itemfor _, item := range items {
	all = append(all, &item)
}
登入後複製

這段程式碼有問題嗎?變數 all 內的 item 變數,儲存進去的是什麼? 是每次迴圈的 item 值嗎?

實際上在 for 迴圈時,每次存入變數 all 的都是相同的 item,也就是最後一個迴圈的 item 值。

這是 Go 面試裡經常出現的題目,結合 goroutine 更風騷,畢竟還會存在亂序輸出等問題。

如果你想解決這個問題,就需要把程式改寫成如下:

var all []*Itemfor _, item := range items {
	item := item
	all = append(all, &item)
}
登入後複製

要重新宣告一個 item 變數把 for 迴圈的 item 變數給儲存下來再追加進去。

  • 案例二:閉包函數

接下來是第二個案例的程式碼:

var prints []func()for _, v := range []int{1, 2, 3} {
	prints = append(prints, func() { fmt.Println(v) })
}for _, print := range prints {	print()
}
登入後複製

這段程式的輸出結果是什麼?沒有 & 取地址符,是輸出 1,2,3 嗎?

輸出結果是 3,3,3。這又是為什麼?

問題的重點之一,關注到閉包函數,實際上所有閉包都列印的是相同的 v。輸出 3,是因為在 for 迴圈結束後,最後 v 的值被設定為了 3,僅此而已。

如果想要達到預期的效果,依然是使用萬能的再賦值。改寫後的程式碼如下:

for _, v := range []int{1, 2, 3} {
		v := v
		prints = append(prints, func() { fmt.Println(v) })
	}
登入後複製

增加 v := v 語句,程式輸出結果為 1,2,3。 仔細翻翻你寫過的 Go 工程,是不是都很熟悉?就這改造方法,贏了。

尤其是配合上 Goroutine 的寫法,很多同學會更容易在此翻車。

解決方案

  • 修復思路

實際上 Go 核心團隊在內部和社群已經討論過許久,希望重新定義 for 迴圈的語法。要達到的目的是:使迴圈變數每次迭代而不是每次迴圈

解決的辦法是:在每個迭代變數 x 的每個迴圈體開頭,加一個隱式的再賦值,也就是 x := x,就能夠解決上述程式中所隱含的坑。和我們現在做的一樣,只不過我們是自己手動加的,Go 團隊做的是希望在編譯器內隱式處理。

  • 讓使用者自己決定

比較尷尬的是 Go 團隊在 Proposal: Go 2 transition 中禁止重新定義語言,所以 rsc 不能直接這麼幹。

因此將會由使用者自己決定控制這個 「破壞」,方式將會是根據每個包的 go.mod 檔案中的 go 行更改語意。

如果我們是在 Go1.30 對本文討論的 for 迴圈改為迭代,那麼在 go.mod 檔案中的 go 版本宣告是將是一個關鍵。

如下圖示:

php入門到就業線上直播課:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:

Go 1.30 或更高版本將會每次迭代變數,而早期 Go 版本的將每次迴圈變數。

如此一來上述提到的 for 迴圈問題都會在一定範圍內被解決。

總結

for 迴圈時的變數問題,一直是各大 Go 考官愛考的題目,另外也確實在實際程式設計 Go 程式碼時會遇到這類坑。

雖然 rsc 希望在 go.mod 檔案上開創先河,利用 go 版本的宣告,去修改語意(不允許新增和刪除)。這無疑是給 Go1 相容性保障開了一個後門。

如果實施,本次變更會導致 Go 的前後版本語意有所不同。還不如變成一個 go.mod 檔案的一個語意開關。

這顯然是一個很折騰的思考題。

以上就是Go語言中的for迴圈有多坑?的詳細內容,更多請關注TW511.COM其它相關文章!