看了好多文章都发现了 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 循环只是为了遍历获取值,而不能遍历修改。
网友评论