Go-map、切片、数组循环常见问题总结

103 阅读3分钟

前绪

开发人员在日常开发中经常会遇到for range循环需求,因其类型的特殊性,用起来有别于其他语言,需要注意,这里对map、切片、数组循环遇到常见问题进行整理。

map

1、for range map 在开始执行循环的时候,底层做了随机种子,故其循环是随机的。

package main
import "fmt"
func main() {
    a := map[int]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for _, c := range a {
        fmt.Println(c)
    }
}

输出:
3
4
5
1
2
多次执行,结果不同

数组

package main

import "fmt"

func main() {

    a := [5]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for b, c := range a {
        if b == 0 {
            a[1] = 22
            //fmt.Println(b)
        }
        a[b] = c + 10
    }
    fmt.Println(a)
}

输出:
[11 12 13 14 15]

//a[1] = 22 并没有起作用,因数组是值类型,使用range获取的数组项是复制过来的,并非引用原始数据。

如果想a[1] = 22 有效,只需改成引用。

package main

import "fmt"

func main() {

    a := [5]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    
    for b, c := range &a {//注意此处改变
        if b == 0 {
            a[1] = 22
            //fmt.Println(b)
        }
        a[b] = c + 10
    }
    fmt.Println(a)
}

结果:
[11 32 13 14 15]

slice

slice 是引用类型,底层指针指向数组,传参的时候传引用和传值区别不大。

示例1

package main

import "fmt"

func main() {

    a := []int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for b, c := range a {
        if b == 0 {
            a[1] = 22
            //fmt.Println(b)
        }
        a[b] = c + 10
    }
    fmt.Println(a)
}

结果:
[11 32 13 14 15]
对比上一个例子,不需要传引用就可以更改a[1] = 22

示例2

这个是输出异常的示例

package main

import (
"fmt"
)

func main() {

    a := []int{1, 2, 3}
    b := make([]*int, len(a))

    for i, v := range a {
        //解决办法1增加临时变量
        //vv := v
        //b[i] = &vv
        //解决办法2   
        //b[i] = &a[i]
        b[i] = &v
    }
    for _, v := range b {
        fmt.Println(*v)
    }
}

输出:
3
3
3

原因v变量在for range中只会创建一次,之后迭代重复使用

迭代闭包容易出问题

package main

import (
    "fmt"
    "time"
)

func main() {
    a := []int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for _, c := range a {
    
        go func() {
            fmt.Println(c)
        }()
    }
    time.Sleep(3 * time.Second)

}

结果为:
3
3
5
5
5
有两种方法解决以上问题

1、增加一个临时变量

package main

import (
    "fmt"
    "time"
)

func main() {
    a := []int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for _, c := range a {
        cc:=c //增减临时变量cc
        go func() {
            fmt.Println(cc)
        }()
    }
    time.Sleep(3 * time.Second)

}

2、 传参进去

package main

import (
    "fmt"
    "time"
)

func main() {
    a := []int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}
    for _, c := range a {
        go func(c int) {
            fmt.Println(c)
        }(c)
    }
    time.Sleep(3 * time.Second)

}

循环子协程

package main

import (
    "fmt"
    "time"
)

type v struct {
    value int
}

func (vv *v) show() {
    fmt.Println(vv.value)
}
func main() {
    a := []v{{1}, {2}, {3}}
    for _, c := range a {
        go c.show()
    }
    time.Sleep(3 * time.Second)
}

输出:
3
3
3
解决办法
1、增加临时变量

package main

import (
    "fmt"
    "time"
)

type v struct {
    value int
}

func (vv *v) show() {
    fmt.Println(vv.value)
}
func main() {
    a := []v{{1}, {2}, {3}}
    for _, c := range a {
        c := c //增加这一行
        go c.show()
    }
    time.Sleep(3 * time.Second)
}

2、使用指针

package main

import (
    "fmt"
    "time"
)

type v struct {
    value int
}

func (vv *v) show() {
    fmt.Println(vv.value)
}
func main() {
    a := []*v{{1}, {2}, {3}}
    for _, c := range a {
        go c.show()
    }
    time.Sleep(3 * time.Second)
}

3、方法show接受者不用指针

package main

import (
    "fmt"
    "time"
)

type v struct {
    value int
}

func (vv v) show() {
    fmt.Println(vv.value)
}
func main() {
    a := []v{{1}, {2}, {3}}
    for _, c := range a {
        go c.show()
    }
    time.Sleep(3 * time.Second)
}