美文网首页
Go 语言中的 go range 循环

Go 语言中的 go range 循环

作者: 星流星 | 来源:发表于2019-10-08 11:33 被阅读0次

    看了好多文章都发现了 go 语言中的 defer 和 go range 循环的坑点,但是很多文章都没有发现这一坑点的实质,下面先简单说一下这个坑点。

    有下面的程序:

    package main
    
    import "fmt"
    
    type student struct {
        id   string
        name string
        sex  rune
    }
    
    func (s *student) Show() {
        fmt.Println("id:", s.id, "name:", s.name, "sex:", s.sex)
    }
    
    func main() {
        ss := []student{
            {"1", "张三", '男'},
            {"2", "李四", '女'},
            {"3", "王五", '女'},
            {"4", "赵六", '男'},
            {"5", "孙七", '女'},
        }
        for _, s := range ss {
            defer s.Show()
        }
    }
    

    上面的程序可能大多数人预计都是下面这样的:

    id: 5 name: 孙七 sex: 22899
    id: 4 name: 赵六 sex: 30007
    id: 3 name: 王五 sex: 22899
    id: 2 name: 李四 sex: 22899
    id: 1 name: 张三 sex: 30007
    

    但是,实际确实:

    id: 5 name: 孙七 sex: 22899
    id: 5 name: 孙七 sex: 22899
    id: 5 name: 孙七 sex: 22899
    id: 5 name: 孙七 sex: 22899
    id: 5 name: 孙七 sex: 22899
    

    有人总结这是 defer 的一个坑点,但是其实这不是 defer 的一个坑点,而是没有理解 go range 循环的实质。

    首先我们先分析一下这个情况的问题所在。defer 语句在执行的时候,会复制参数的值,然后将这些信息压入栈中。但是这里的 Show 方法的选择子是一个指针类型,那么这里记录的就会是一个指针的地址,而不是指针指向的值的地址。下面我们去看看 go range 循环中的 s 的 地址。

    func main() {
        ss := []student{
            {"1", "张三", '男'},
            {"2", "李四", '女'},
            {"3", "王五", '女'},
            {"4", "赵六", '男'},
            {"5", "孙七", '女'},
        }
    
        for _, s := range ss {
            fmt.Printf("%p\n", &s)
        }
    }
    

    你会惊讶的发现它的结果是类似下面这样的:

    0xc00006e150
    0xc00006e150
    0xc00006e150
    0xc00006e150
    0xc00006e150
    

    那么现在要解释上面的那种情况就简单了,同一个地址,循环的最后一次这个地址执行了 ss 的最后一个元素,所以打印出来的都是相同的。那么 for range 循环为什么回这样呢?

    这里猜测它的做法是这样的:

    func main() {
        ss := []student{
            {"1", "张三", '男'},
            {"2", "李四", '女'},
            {"3", "王五", '女'},
            {"4", "赵六", '男'},
            {"5", "孙七", '女'},
        }
    
        var s student
        for i := 0; i < len(ss); i++ {
            s = ss[i]
            fmt.Printf("%p\n", &s)
            defer s.Show()
        }
    }
    

    也不难理解,因为在 Go 语言中的 = 会进行复制。

    下面还有一个坑点就是使用 for range 循环修改 struct 中的数据。

    for _, s := range ss {
        s.name = "张三"
    }
    fmt.Println(ss)
    

    结果如下:

    [{1 张三 30007} {2 李四 22899} {3 王五 22899} {4 赵六 30007} {5 孙七 22899}]
    

    所以 for range 循环只是为了遍历获取值,而不能遍历修改。

    相关文章

      网友评论

          本文标题:Go 语言中的 go range 循环

          本文链接:https://www.haomeiwen.com/subject/dkfbpctx.html