Maison  >  Article  >  développement back-end  >  Enregistrez les pièges de la traversée de la boucle de Go

Enregistrez les pièges de la traversée de la boucle de Go

藏色散人
藏色散人avant
2021-02-19 17:38:232293parcourir
Ci-dessous

Golang La colonne tutoriel partagera avec la boucle de GO pour utiliser la petite fosse, j'espère que cela sera utile aux amis dans le besoin !

Enregistrez les pièges de la traversée de la boucle de Go

Dans le contrôle de flux de Golang, il existe deux types d'instructions de boucle : for et range.

pour déclaration

1.for 赋值表达式; 关系表达式或逻辑表达式; 赋值表达式 { }

for i := 0; i d42e6976ffa8c068cfeb897ebdc655b0 0 {
 n--}

3.for { }

for {
 fmt.Println("hello world")
}
// 等价于
// for true {
//     fmt.Println("hello world")
// }

instruction range

La plage Golang est similaire à une opération d'itérateur et peut parcourir des tranches, des cartes, des tableaux, des chaînes, etc. Il renvoie (index, valeur) dans les chaînes, les tableaux et les tranches, et (clé, valeur) dans les collections, mais lorsqu'il n'y a qu'une seule valeur de retour, le premier argument est l'index ou la clé.

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

La boucle for, en particulier l'instruction range, est fréquemment utilisée dans le processus de développement quotidien, mais de nombreux développeurs (dont moi-même) tombent souvent dans le piège dans les scénarios suivants.

Scénario 1, utilisation de variables d'itérateur de boucle

Regardons d'abord une erreur évidente :

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

Analyse

est une variable de tableau de pointeurs entiers. Dans la boucle for, une variable out est déclarée. Chaque boucle ajoute l'adresse de i à la tranche i, mais chaque fois qu'elle est ajoutée, elle est en réalité ajoutée. out variables, donc ce que nous ajoutons est la même adresse, et la valeur finale de l'adresse est 3. i

Approche correcte

Débloquez les commentaires

dans le code et recréez une nouvelle // i := i variable à chaque fois dans la boucle. i


Regardons une erreur plus subtile :

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

Analyse

La plupart des gens attribuent des valeurs aux variables ici

Parfois Je marche sur le piège car il est relativement secret. En fait, la situation est la même que ci-dessus Lorsque range traverse le type valeur, le range est une variable locale. Elle ne sera déclarée et initialisée qu'une seule fois. puis réaffecté à chaque fois qu'il boucle pour écraser le précédent, donc lors de l'attribution de valeurs à v, elles ont en fait toutes la même adresse a2[i], et la valeur finale de &v est la valeur de la dernière. élément de v, qui vaut 3. a1

Approche correcte

Passez le pointeur d'origine lors de l'affectation, c'est-à-dire a2[i]a2[i] = &a1[i]② Créez une variable temporaire
 ; 🎜>t := v③ Fermeture (identique au principe ②), a2[i] = &t
func(v int) { a2[i] = &v }(v)

Plus secrètement :
func main() {
    var out [][]int
    for _, i := range [][1]int{{1}, {2}, {3}} {
        out = append(out, i[:])
    }
    fmt.Println("Values:", out)}// 输出结果// [[3] [3] [3]]

Le principe est le même, peu importe le nombre de fois qu'il est parcouru ,

Toujours écrasé par la valeur de ce parcours

i[:]

Scénario 2, utiliser des goroutines dans le corps de la boucle
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

Analyse

Pour la coroutine principale, la boucle se termine rapidement et chaque coroutine ne peut commencer à s'exécuter qu'à ce moment-là, la valeur de
a été parcourue jusqu'à la dernière, donc chaque coroutine a une sortie . (Si les données de parcours sont énormes et que le parcours de la coroutine principale prend beaucoup de temps, la sortie de la goroutine sera basée sur la valeur de

à ce moment-là, donc le résultat de sortie n'est pas nécessairement le même à chaque fois.) val3valSolution

①Utiliser une variable temporaire

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer