Go 语言中的 go range 循环

2019-10-08  本文已影响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 循环只是为了遍历获取值,而不能遍历修改。

上一篇下一篇

猜你喜欢

热点阅读