Golang 中 for-range 的坑

             

1. 指针数据坑

range到底有什么坑呢,我们先来运行一个例子吧。

package main

import (
    "fmt"
)

type user struct {
 name string
 age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    n := make([]*user,0,len(u))
    for _,v := range u{
        n = append(n, &v)
    }
    fmt.Println(n)
    for _,v := range n{
        fmt.Println(v)
    }
}

这个例子的目的是,通过u这个slice构造成新的slice。我们预期应该是显示uslice的内容,但是运行结果如下:

[0xc0000a6040 0xc0000a6040 0xc0000a6040]
&{asong2020 18}
&{asong2020 18}
&{asong2020 18}

这里我们看到n这个slice打印出来的三个同样的数据,并且他们的内存地址相同。这是什么原因呢?先别着急,再来看这一段代码,我给他改正确他,对比之后我们再来分析,你们才会恍然大悟。

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    n := make([]*user,0,len(u))
    for _,v := range u{
        o := v
        n = append(n, &o)
    }
    fmt.Println(n)
    for _,v := range n{
        fmt.Println(v)
    }
}

细心的你们看到,我改动了哪一部分代码了嘛?对,没错,我就加了一句话,他就成功了,我在for range里面引入了一个中间变量,每次迭代都重新声明一个变量o,赋值后再将v的地址添加n切片中,这样成功解决了刚才的问题。

现在来解释一下原因:在for range中,变量v是用来保存迭代切片所得的值,因为v只被声明了一次,每次迭代的值都是赋值给v,该变量的内存地址始终未变,这样讲他的地址追加到新的切片中,该切片保存的都是同一个地址,这肯定无法达到预期效果的。这里还需要注意一点,变量v的地址也并不是指向原来切片u[2]的,因我在使用range迭代的时候,变量v的数据是切片的拷贝数据,所以直接copy了结构体数据。

上面的问题还有一种解决方法,直接引用数据的内存,这个方法比较好,不需要开辟新的内存空间,看代码:

......略
for k,_ := range u{
    n = append(n, &u[k])
}
......略


2. 迭代修改变量问题

还是刚才的例子,我们做一点改动,现在我们要对切片中保存的每个用户的年龄进行修改,因为我们都是永远18岁,嘎嘎嘎~~~。

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    for _,v := range u{
        if v.age != 18{
            v.age = 20
        }
    }
    fmt.Println(u)
}

来看一下运行结果:

[{asong 23} {song 19} {asong2020 18}]

哎呀,怎么回事。怎么没有更改呢。其实道理都是一样,还记得,我在上文说的一个知识点嘛。对,就是这个,想起来了吧。v变量是拷贝切片中的数据,修改拷贝数据怎么会对原切片有影响呢,还是这个问题,copy这个知识点很重要,一不注意,就会出现问题。知道问题了,我们现在来把这个问题解决吧。

package main

import (
    "fmt"
)


type user struct {
    name string
    age uint64
}


func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    for k,v := range u{
        if v.age != 18{
            u[k].age = 18
        }
    }
    fmt.Println(u)
}

可以看到,我们直接对切片的值进行修改,这样就修改成功了。所以这里还是要注意一下的,防止以后出现bug


3. 是否会造成死循环

来看一段代码:

func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}

这一段代码会造成死循环吗?答案:当然不会,前面都说了range会对切片做拷贝,新增的数据并不在拷贝内容中,并不会发生死循环。这种题一般会在面试中问,可以留意下的。



发表评论 请登录再评论
  •   文章分类
回到顶部