range 赋值问题详解

在使用go语言编程的过程中,少不了使用range遍历数组,字典等。有时需要在遍历的过程中修改相关值,不正确的使用会导致更新失败。举个例子,运行下图代码时,最后结果会全部是最后一个结构体的值。

type Model struct {
    Val int
}

func main() {
    var a = []Model{{Val: 1},{Val: 2},{Val: 3},{Val: 4}}
    var b = make([]*Model, len(a))
    for k, v := range a {
        b[k] = &v
    }
    for _, v := range b {
        fmt.Println(v.Val)
    }
}

output:

4
4
4
4

这里面涉及到一个问题,对于for k, v := range a这个v到底是什么东西。这个v可以简单理解为一个地址不变的引用,在遍历过程中,v的指针地址始终不变,但指针所指向的值会随着遍历的进行而改变。因此b[k]的值都是a的最后一个元素的值。
这种情况只需要把b[k] = &v换成b[k] = v就行,避免直接取指针地址,而是进行值复制。当然需要对b的类型进行相应的改变。但有时候数据结构不允许我们做这样的改变,那么有没有其它方法可以避免这种问题呢?也是有的,可以使用临时变量做一层中间转换,如下图

type Model struct {
    Val int
}

func main() {
    var a = []Model{{Val: 1},{Val: 2},{Val: 3},{Val: 4}}
    var b = make([]*Model, len(a))
    for k, v := range a {
        tmp := v
        b[k] = &tmp
    }
    for _, v := range b {
        fmt.Println(v.Val)
    }
}

output:

1
2
3
4

这里将v赋值给tmp,tmp是一个临时变量,其指针地址在每一轮遍历都会改变,这样b[k]在赋值的时候就不会赋值到同一个指针地址。
range有很多使用场景,例如遍历channel, map, slice,但使用记住一点,range返回的v,它始终是一个指针地址不变的变量,因此在赋值过程中需要额外考虑对v进行修改或者赋值是否符合预期。

你可能感兴趣的:(range 赋值问题详解)