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。我们预期应该是显示 u slice 的内容,但是运行结果如下:
[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 会对切片做拷贝,新增的数据并不在拷贝内容中,并不会发生死循环。这种题一般会在面试中问,可以留意下的。