Heim  >  Artikel  >  Backend-Entwicklung  >  Notieren Sie die Fallstricke von Gos Schleifendurchquerung

Notieren Sie die Fallstricke von Gos Schleifendurchquerung

藏色散人
藏色散人nach vorne
2021-02-19 17:38:232292Durchsuche

-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!

Notieren Sie die Fallstricke von Gos SchleifendurchquerungIn der Prozesssteuerung von Golang gibt es zwei Arten von Schleifenanweisungen: for und range.

für Anweisung

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

range语句

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

for循环尤其是range语句,在平时开发过程中频繁使用,但很多开发者(本人算一个)经常会在以下场景中踩坑。

场景一,使用循环迭代器的变量

先来看一个明显的错误:

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 := va2[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[:]总是被本次遍历的值所覆盖

场景二,在循环体内使用goroutines

for _, val := range values {
    wg.Add(1)
    go func(val int) {
        fmt.Println(val)
        wg.Done()
    }(val)}

分析

对于主协程来讲,循环是很快就跑完的,而这个时候各个协程可能才开始跑,此时val的值已经遍历到最后一个了,所以各协程都输出了3。(如果遍历数据庞大,主协程遍历耗时较久的话,goroutine的输出会根据当时候的val

Der Golang-Bereich ähnelt einer Iteratoroperation und kann über Slices, Karten, Arrays, Strings usw. iterieren. Es gibt (Index, Wert) in Strings, Arrays und Slices und (Schlüssel, Wert) in Sammlungen zurück, aber wenn es nur einen Rückgabewert gibt, ist das erste Argument der Index oder Schlüssel.
rrreee

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.

Szenario 1, Verwendung der Variablen des Schleifeniterators

Sehen wir uns zunächst einen offensichtlichen Fehler an: 🎜rrreee🎜🎜Analyse🎜🎜🎜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. 🎜
🎜Werfen wir einen Blick auf einen geheimnisvolleren Fehler: 🎜rrreee🎜🎜Analyse🎜🎜🎜Die meisten Leute machen Fehler, wenn sie den Variablen hier im 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 a1Der 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]
②Erstellen Sie eine temporäre Variable t := v; a2[i] = &t
③ Abschluss (gleiches Prinzip wie ②), func(v int) { a2 [i] = &v }(v)🎜
🎜Das Geheimnisvollere ist: 🎜rrreee🎜Das Prinzip ist das gleiche, egal wie oft es durchlaufen wird, 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🎜rrreee

Das 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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen