孤独的感觉是所有焦躁的根源。——弗洛姆《爱的艺术》
1. 前言
这篇文章我们来聊聊slice当作参数传递的时候会出现什么问题。还有for range在遍历赋值的时候会出现什么问题。
2. slice传参
package main import "fmt" func main() { slice := []int{1, 2} fmt.Printf("data:%v, len:%d, cap:%d\n", slice, len(slice), cap(slice)) updateslice(slice) fmt.Printf("after data:%v, len:%d, cap:%d\n", slice, len(slice), cap(slice)) } func updateslice(slice []int) { slice[0] = 12 slice = append(slice, 10) fmt.Printf("inner data:%v, len:%d, cap:%d\n", slice, len(slice), cap(slice)) }
执行结果:
data:[1 2], len:2, cap:2 inner data:[12 2 10], len:3, cap:4 after data:[12 2], len:2, cap:2
由于切片是引用类型,所以数据修改会影响外部,实际上内外是指向同一个底层数据。但是当对updateslice函数做append操作后,发现内部的添加数据修改并没有影响到外部变量,此时可以看到内部的切片容量发生了扩容,之前的数据会被copy一份到新的地址slice,此时该扩容后的切片指向的是新的地址slice,函数内外切片并不是指向同一个底层数据。
总结:当数据调整发生扩容时,内外切片不是指向同一个底层数据;否则,指向同一个底层数组。
3. for range
type student struct { name string age int } func main() { m := make(map[string]*student) stus := []student{ {name: "小王子", age: 18}, {name: "娜扎", age: 23}, {name: "大王八", age: 9000}, } // 这里出现问题 for _, stu := range stus { m[stu.name] = &stu } for k, v := range m { fmt.Println(k, "=>", v.name) } }
对于该代码,我们的预期结果是
娜扎 => 娜扎 大王八 => 大王八 小王子 => 小王子
但是得到的结果是:
小王子 => 大王八 娜扎 => 大王八 大王八 => 大王八
原因是for遍历时,变量stu是值的副本,每次遍历仅进行struct值拷贝,它指向的地址不变,所以每一次操作m[stu.name] = &stu,实际指向的都是同一个地址,遍历完成后,存储的是结构体最后一个值的拷贝。
根本原因在于for-range会使用同一块内存去接收循环中的值。
也就是说stu指向的地址是同一个,但是值是拷贝过来的,最后大家引用的是同一个地址的值。
修改如下:
for _, stu := range stus { value := stu //拷贝一份就可以了 m[stu.name] = &value }
4. 小结
slice传递参数我们应当注意扩缩容问题,没有扩缩容,还是之前的slice,否则是新的slice。for range注意赋值问题,不要企图将for中的变量赋值给别的结构,这样会导致引用的是同一份数据。
5. 关注公众号
微信公众号:堆栈future