golang 关于 forrange 的一些疑问

查看 96|回复 11
作者:main1234   
package main
import "fmt"
type Users struct {
        Name string
}
func (u *Users) GetName() {
        fmt.Println(u.Name)
}
func main() {
        users1 := []Users{{"a"}, {"b"}, {"c"}}
        for _, u := range users1 {
                defer u.GetName() //c  c  c
        }
        users2 := []*Users{{"x"}, {"y"}, {"z"}}
        for _, u := range users2 {
                defer u.GetName() //z x y
        }
}
我理解 forrange 的原理,无论第一个还是第二个 forrange ,u 都是同一个地址
对于第一个 forrange ,由于 u 是同一个地址,for 执行完毕后 u 地址指向最后一个{"c"},所以输出的都是 ccc
对于第二个 forrange ,我想不明白,for 循环完后,u 不是也是指向最后一个{"z"},那么输出的为啥不是 zzz
求大佬赐教
gerorim   
在第一个 for 中,u 是 users1 数组中每个元素的副本。当使用 defer 时,scheduler 对 GetName()的调用在函数末尾执行(即 main())。重要的是,defer 捕获变量 u 本身,而不是 u 在每次迭代时指向的值。因为 u 是一个结构(不是指针),它会被循环的每次迭代覆盖,到 main()退出时,u 为循环的最后一个值,即{Name: "c"}。因此,GetName ()打印“c”三次。
gerorim   
第二个 for ,u 是一个指针,直接指向用户 2 切片中的每个元素。同样,defer 捕获 u ,但在这里,每个 u 都是一个不同的指针,指向不同的地址。循环分别捕获每个指针,当延迟执行对 GetName()的调用时,每个指针都指向不同的用户结构。此外,由于延迟执行 LIFO ( Last In ,First Out )顺序的函数,所以应该看到“z”、“y”、“x”(循环顺序的反转),而不是楼主所要的“z”、“z”、“z”。
twl007   
Fixing For Loops in Go 1.22
https://go.dev/blog/loopvar-preview
twl007   
第一个行为在 1.22 修复了
在 go mid 里面的的版本小于 1.22 的时候会继续保持以前的行为 在版本大于 1.22 的时候会修正这个问题
lance6716   
因为你的 GetName 定义在 *Users 上,当变量是类型 Users 会有一个隐含的取地址,再加上旧版本 for loop 用的是同一个地址,就会变成 ccc
povsister   
一楼说的属于是牛头不对马嘴了… 这两个 for 没有本质区别,都是在不停对局部变量 u 进行赋值,op 疑惑的这个问题其实隐含了 3 个问题:
1. defer 的本质是什么?
2. go 编译器的自动 takeRef/deRef
3. func with receiver 到底是什么?
简单来说你可以认为,defer 会把函数压入栈中,而且函数参数的 evaluate 发生在 defer 语句那一行
所以 循环一的实际 defer 是这样的
defer GetName(&u)
循环二代实际 defer 是
defer GetName(u)
注意两个&的区别,再结合我说的,参数 evaluate 发生在 defer 语句书写时,这下 ,op 理解了否?
留个思考题。
defer func() { u.GetName() }
这个输出什么呢?(笑
lance6716   
第二个 for loop:defer 并不是闭包,所以跟你说的“指向最后一个”没关系。你是直接被第一个 for loop 搞迷糊了,误以为自己掌握了某个坑能解释这个奇怪行为,其实只是瞎猫碰上死耗子
建议升级到 go1.22 直接避免踩坑
main1234
OP
  
@povsister 我有点懵了,对于第二个 forrange 来说,defer 相当于压栈,底层是个链表,u 的地址是同一个,链表中 u 指向的地址难道不是最后一个结构体的地址????
main1234
OP
  
@povsister 执行三次 defer ,相当于创建了 3 个链表节点,每个链表节点中 u 是同一个地址;当第一次执行 defer ,链表只有一个节点,u 指向结构体第一个元素;然后第二次执行 defer ,第一个链表节点 u 指向的元素不会变成第二个结构体元素么??
您需要登录后才可以回帖 登录 | 立即注册

返回顶部