? ? ? ? 本文的知識點其實由golang知名的for循環陷阱發散而來, 對應到我的主力語言C#, 其實牽涉到閉包、foreach。為了便于理解,我重新組織了語言,以倒敘結構行文。
先給大家提煉出一個C#題:觀察for、foreach閉包的差異
左邊輸出 5個5;右邊輸出0,1,2,3,4, 答對的、不屑看題的同學都可以出門右轉了。
閉包是在詞法環境中捕獲自由變量的頭等函數, 題中關鍵是捕獲的自由變量。
這里面有3個關鍵名詞,希望大家重視,可以圍觀我之前的 👇新來的總監,把C#閉包講得那叫一個透徹[1]。
demo1
for循環內閉包,局部變量i是被頭等函數引用的自由變量;相對于每個頭等函數,i是全局變量;
閉包捕獲變量i的時空和 閉包執行的時空不是一個時空;
所有閉包執行時,捕獲的都是變量i,所以執行輸出的都是
i++
最后的5。
這也是C#閉包的陷阱, 通常應對方式是循環內使用一個局部變量解構每個閉包與(相對全局)變量i的關系。
var?t1?=?new?List<Action>();for?(int?i?=?0;?i?<?5;?i++){//?使用局部變量解綁閉包與全局自由變量i的關系,現在自由變量是局部變量j了。var?j?=?i;var?func?=?(()?=>{Console.WriteLine(j);});t1.Add(func);}foreach?(var?item?in?t1){item();}
demo2
foreach內閉包,為什么能輸出預期的0,1,2,3,4。
聰明的讀者可以猜想,是不是foreach在循環迭代時 ,給我們搞出了局部變量j,幫我們解構了閉包與全局自由變量i多對1的關系。
foreach的底層實現有賴于IEnumerable
和IEnumerator
兩個接口的實現、
這里也有一個永久更新的原創文,👇IEnumerator、IEnumerable還傻傻分不清楚?[2]
但是怎么用這個兩個接口,還需要看foreach偽代碼,??
C# foreach foreach (V v in x) ?embedded_statement?
被翻譯成下面代碼:
{E?e?=?((C)(x)).GetEnumerator();try{while?(e.MoveNext()){V?v?=?(V)(T)e.Current;?//?注意,?變量v的定義是在循環體內?embedded_statement?}}finally{...?//?Dispose?e}
}
👇foreach官方信源[3]
請注意注釋,變量v的定義是在while循環內部, 因此使用foreach迭代時,每個閉包捕獲的都是局部的自由變量, 因此foreach閉包執行能輸出0,1,2,3,4。
如果變量V v定義在while語言上方,那么效果就和for循環一樣了。
這是for循環/foreach迭代一個很有意思的差異。
再來看看引發我思考的Golang的for循環陷阱, Golang只有for循環,沒有while,foreach關鍵字。?
package?mainimport?"fmt"var?slice?[]func()
//for循環產生閉包切片
func?main()?{sli?:=?[]int{1,?2,?3,?4,?5}for?_,?v?:=?range?sli?{fmt.Println(&v,?v)slice?=?append(slice,?func()?{fmt.Println(v)?})}for?_,?val?:=?range?slice?{val()}
}
---?output?---
0xc00001c098?1
0xc00001c098?2
0xc00001c098?3
0xc00001c098?4
0xc00001c098?5
5
5
5
5
5
golang for循環作用在切片上,使用姿勢類似于C#的 foreach,但是內核卻是c# for循環。
每個閉包引用的都是(相對全局的)自由變量v,最終閉包拿到的是一個變量的最終值。
應對這種陷阱的思路,依舊是使用循環內局部變量去解構閉包與(相對全局)z自由變量v的關系。
畫外音
本文其實內容很多:
閉包:是在詞法環境中捕獲自由變量的頭等函數
foreach 語法糖:依賴于IEnumerable和IEnumerator 接口實現,同時 foreach每次迭代使用的是塊內局部變量, for循環變量是相對的全局變量, 也正是這個差異,導致了投票題的結果。
每一個知識點都是重要且晦澀,篇幅有限,請適時關注文中給出的幾個永久更新地址,也請各大佬斧正,協助我永久更新????。
參考資料
[1]
👇新來的總監,把C#閉包講得那叫一個透徹: https://www.cnblogs.com/JulianHuang/p/14618378.html
[2]👇IEnumerator、IEnumerable還傻傻分不清楚?: https://www.cnblogs.com/JulianHuang/p/14271285.html
[3]👇 foreach官方信源: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#1295-the-foreach-statement