golang踩坑:slice传参,for range赋值,goroutine闭包,append
一、slice的坑
案例:
查看以下代码会输出啥?
func main() {
a := []int{7,8,9}
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
ap(a)
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
}
func ap(a []int) {
a[0] = 1
a = append(a, 10)
}
答案:
len: 3 cap:3 data:[7 8 9] len: 3 cap:3 data:[1 8 9]
解析:
这时ap后再输出a,会看到a[0]变成了1,但a的cap依然是3,看起来10并没有被append进去?原因很简单,Go中没有引用传递全是值传递,值传递意味着传递的是数据的拷贝,但是用的是一个地址
这实际上并不是匪夷所思,因为Go和C不一样,slice看起来像数组,实际上是一个结构体,在源码中的数据结构是:
type slice struct {
array unsafe.Pointer
len int
cap int
}
这个结构体其实也很好理解,array是一个真正的数组指针,指向一段连续内存空间的头部,len和cap代表长度和容量。
可以把ap(a)替换成ap(array: 0x123, len: 3, cap: 3),这样就比较好理解了,append修改的是数据的拷贝,但是a[0]=1修改的是地址的值
二、for range的坑
案例1:
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)
}
}
对于该代码,我们的预期结果是:
娜扎 => 娜扎 大王八 => 大王八 小王子 => 小王子
结果是:
小王子 => 大王八 娜扎 => 大王八 大王八 => 大王八
案例2:
func main() {
arr1 := []int{1, 2, 3}
arr2 := make([]*int, len(arr1))
for i, v := range arr1 {
arr2[i] = &v
}
for _, v := range arr2 {
fmt.Println(*v)
}
}
预期输出:
1 2 3
结果输出:
3 3 3
原因解析:
因为for range在遍历值类型时,其中的v变量是一个值的拷贝,当使用&获取指针时,实际上是获取到v这个临时变量的指针,而v变量在for range中只会创建一次,之后循环中会被一直重复使用,所以在arr2赋值的时候其实都是v变量的指针,而&v最终会指向arr1最后一个元素的值拷贝
三、Goroutine中捕获参数
goroutine中捕获的循环变量, 都为循环最后的值。
func main() {
for i, v := range []string{"a", "b", "c", "d", "e"} {
// goroutine中捕获循环变量
go func() {
fmt.Printf("index: %v, value: %v\n", i, v)
}()
}
// 此处应该使用waitgroup实现, 为了简单使用了sleep
time.Sleep(1 * time.Second)
}
//================输出==============
index: 4, value: e
index: 4, value: e
index: 4, value: e
index: 4, value: e
index: 4, value: e
原因:
goroutine中捕获的不是"值", 而是"有地址的变量". for循环可能会先结束, 之后各个goroutine才开始执行. 因此得到的是变量的最终值。
避免方式 在goroutine启动的函数中, 把变量作为参数捕获。
func main() {
for i, v := range []string{"a", "b", "c", "d", "e"} {
// 把循环变量作为参数传入
go func(i int, v string) {
// i, v是函数内部的局部变量
fmt.Printf("index: %v, value: %v\n", i, v)
}(i, v)
}
time.Sleep(1 * time.Second)
}
//================输出==============
index: 0, value: a
index: 1, value: b
index: 4, value: e
index: 3, value: d
index: 2, value: c
四、append踩坑
案例1:
package main
import "fmt"
type C struct {
A []int
}
func main() {
c := C{}
c.B()
c.C()
fmt.Println(c.A)
}
func (receiver C) B() {
receiver.A = append(receiver.A, 1)
fmt.Println(receiver)
}
func (receiver C) C() {
receiver.A = append(receiver.A, 2)
fmt.Println(receiver)
}
输出:
{[1]}
{[2]}
[]
原因:receiver 是C的拷贝,所以,他的修改不影响原值,正确写法:改成指针接收者
package main
import "fmt"
type C struct {
A []int
}
func main() {
c := C{}
c.B()
c.C()
fmt.Println(c.A)
}
func (receiver *C) B() {
receiver.A = append(receiver.A, 1)
}
func (receiver *C) C() {
receiver.A = append(receiver.A, 2)
}
案例2:
package main
import "fmt"
func main() {
i := new([]string)
a(i)
b(i)
fmt.Println(*i)
}
func a(i *[]string) {
c := append(*i, "a")
i = &c
}
func b(i *[]string) {
c := append(*i, "b")
i = &c
}
输出:[]
修改方法:i = &c

浙公网安备 33010602011771号