Heim >Backend-Entwicklung >Golang >Notieren Sie die Fallstricke von Gos Schleifendurchquerung
-Tutorialspalte mit der Schleife von GO geteilt, um die kleine Grube zu verwenden, ich hoffe, dass es den Freunden, die es brauchen, hilfreich sein wird!
In der Prozesssteuerung von Golang gibt es zwei Arten von Schleifenanweisungen: for und range.
1.für relationalen Ausdruck oder logischen Ausdruck; code><span class="header-link octicon octicon-link"><pre class="brush:php;toolbar:false">for i := 0; i ffc6ac104df79bec653289ede08cc008 0 {
n--}</pre>
<p>range-Anweisung<code>1.for 赋值表达式; 关系表达式或逻辑表达式; 赋值表达式 { }
for { fmt.Println("hello world") } // 等价于 // for true { // fmt.Println("hello world") // }
2.for 关系表达式或逻辑表达式 { }
str := "abc" for i, char := range str { fmt.Printf("%d => %s\n", i, string(char)) } for i := range str { //只有一个返回值 fmt.Printf("%d\n", i) } nums := []int{1, 2, 3} for i, num := range nums { fmt.Printf("%d => %d\n", i, num) } kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s => %s\n", k, v) } for k := range kvs { //只有一个返回值 fmt.Printf("%s\n", k) } // 输出结果 // 0 => a // 1 => b // 2 => c // 0 // 1 // 2 // 0 => 1 // 1 => 2 // 2 => 3 // a => apple // b => banana // a // b
3.for { }
func main() { var out []*int for i := 0; i < 3; i++ { // i := i out = append(out, &i) } fmt.Println("值:", *out[0], *out[1], *out[2]) fmt.Println("地址:", out[0], out[1], out[2]) } // 输出结果 // 值: 3 3 3 // 地址: 0xc000012090 0xc000012090 0xc000012090
Golang range类似迭代器操作,可以对 slice、map、数组、字符串等进行迭代循环。在字符串、数组和切片中它返回 (索引, 值) ,在集合中返回 (键, 值),但若当只有一个返回值时,第一个参数是索引或键。
func main() { a1 := []int{1, 2, 3} a2 := make([]*int, len(a1)) for i, v := range a1 { a2[i] = &v } fmt.Println("值:", *a2[0], *a2[1], *a2[2]) fmt.Println("地址:", a2[0], a2[1], a2[2]) } // 输出结果 // 值: 3 3 3 // 地址: 0xc000012090 0xc000012090 0xc000012090
先来看一个明显的错误:
func main() { var out [][]int for _, i := range [][1]int{{1}, {2}, {3}} { out = append(out, i[:]) } fmt.Println("Values:", out)}// 输出结果// [[3] [3] [3]]
分析
out
是一个整型指针数组变量,在for循环中,声明了一个i
变量,每次循环将i
的地址追加到out
切片中,但是每次追加的其实都是i
变量,因此我们追加的是一个相同的地址,而该地址最终的值是3。
正确做法
解开代码中的注释// i := i
,每次循环时都重新创建一个新的i
变量。
再看一个比较隐秘的错误:
func main() { values := []int{1, 2, 3} wg := sync.WaitGroup{} for _, val := range values { wg.Add(1) go func() { fmt.Println(val) wg.Done() }() } wg.Wait()}// 输出结果// 3// 3// 3
分析
大多数人就是在range
这里给变量赋值的时候踩坑,因为比较隐秘,其实情况和上面的一样,range
在遍历值类型时,其中的v
是一个局部变量,只会声明初始化一次,之后每次循环时重新赋值覆盖前面的,所以给a2[i]
赋值的时候其实都是同一个地址&v
,而v
最终的值为a1
最后一个元素的值,也就是3。
正确做法
①a2[i]
赋值时传递原始指针,即a2[i] = &a1[i]
②创建临时变量t := v
;a2[i] = &t
③闭包(与②原理一样),func(v int) { a2[i] = &v }(v)
更为隐秘的还有:
for _, val := range values { wg.Add(1) val := val go func() { fmt.Println(val) wg.Done() }()}
原理也是一样的,不论遍历多少次,i[:]
总是被本次遍历的值所覆盖
for _, val := range values { wg.Add(1) go func(val int) { fmt.Println(val) wg.Done() }(val)}
分析
对于主协程来讲,循环是很快就跑完的,而这个时候各个协程可能才开始跑,此时val
的值已经遍历到最后一个了,所以各协程都输出了3
。(如果遍历数据庞大,主协程遍历耗时较久的话,goroutine的输出会根据当时候的val
rrreeeSzenario 1, Verwendung der Variablen des SchleifeniteratorsSehen wir uns zunächst einen offensichtlichen Fehler an: 🎜rrreee🎜🎜Analyse🎜🎜🎜Die for-Schleife, insbesondere die Range-Anweisung, wird im täglichen Entwicklungsprozess häufig verwendet, aber viele Entwickler (einschließlich mir) machen in den folgenden Szenarien häufig Fehler.
out
ist eine ganzzahlige Zeiger-Array-Variable im for Schleife wird eine i
-Variable deklariert, und jede Schleife hängt die Adresse von i
an das out
-Slice an, aber was jedes Mal angehängt wird, ist tatsächlich i
-Variable, also hängen wir dieselbe Adresse an und der Endwert der Adresse ist 3. 🎜🎜🎜Richtiger Ansatz🎜🎜🎜Entsperren Sie den Kommentar // i := i
im Code und erstellen Sie bei jedem Schleifendurchlauf eine neue i
-Variable. 🎜Bereich
Werte zuweisen, weil es relativ ist Tatsächlich ist die Situation dieselbe wie oben: Wenn range
Werttypen durchläuft, ist v
eine lokale Variable. Sie wird nur einmal deklariert und initialisiert und dann neu zugewiesen Jedes Mal, wenn eine Schleife ausgeführt wird, um die vorherige zu überschreiben, haben sie bei der Zuweisung von Werten zu a2[i]
tatsächlich alle dieselbe Adresse &v
und den Endwert von v
ist a1
Der Wert des letzten Elements, nämlich 3. 🎜🎜🎜Richtiger Ansatz🎜🎜🎜①a2[i]
Übergeben Sie beim Zuweisen den ursprünglichen Zeiger, d. h. a2[i] = &a1[i]
t := v
; a2[i] = &t
func(v int) { a2 [i] = &v }(v)
🎜i[ :]
Wird immer durch den Wert dieser Durchquerung überschrieben. Szenario 2: Verwendung von Goroutinen hat gerade mit der Ausführung begonnen. Zu diesem Zeitpunkt wurde der Wert von val
auf den letzten Wert übertragen, sodass jede Coroutine 3
ausgibt. (Wenn die Durchlaufdaten sehr groß sind und der Durchlauf der Haupt-Coroutine lange dauert, basiert die Ausgabe der Goroutine auf dem Wert von val
zu diesem Zeitpunkt, sodass das Ausgabeergebnis möglicherweise nicht dasselbe ist jedes Mal.) 🎜🎜🎜 Lösung🎜🎜🎜①Verwenden Sie temporäre Variablen🎜rrreee🎜②Verwenden Sie Verschlüsse🎜rrreeeDas obige ist der detaillierte Inhalt vonNotieren Sie die Fallstricke von Gos Schleifendurchquerung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!