GO的一些易错踩坑点笔记

最近在看golang语言的代码,体会到了go语言的一些强大之处。

感觉融合了java,python等语言的优点,不想java那么臃肿,一些方法的定义和使用更加方便。

但是如果不深入了解一些特性的原理,可能会造成一些出乎意料的错误,尤其在一些细节上。

在这篇文章中,简单列举一些容易踩坑的点。

for range 与 goroutine

当我们执行下面样例时,预期的效果是打印数组中的每一个元素,,但是实际输出时却都是打印最后一个元素。

css 复制代码
func demo1() {
    var m = [...]int{1, 2, 3, 4, 5}
    for i, v := range m {
        go func() {
            time.Sleep(time.Second * 3)
            fmt.Println(i, v)
        }()
    }
    time.Sleep(time.Second * 10)
}

实际打印结果:

复制代码
4 5
4 5
4 5
4 5
4 5

这是为什么呢?

这是因为goroutine执行的闭包函数引用了它的外层包裹函数中的变量i,v,这样变量i,v在主goroutine和新启动的goroutine之间实现了共享。在各个gorountine中会先等待一段时间,这使i,v已经在外层被替换成新的值,所以没有像预期的那样输出值。

如果要达到预期的效果,应该怎样修改呢?

可以在每个gorountine创建时将当时的i,v进行绑定,或者先赋值给一个临时变量。

for range 进行元素复制

假设有这样一个场景,我们遍历一个切片,对切片中的元素放到一个指针切片

css 复制代码
list2 := []Demo{{"a"}, {"b"}}
    var alist []*Demo
    for _, test := range list2 {
        fmt.Printf("addr=%p\n", &test)
        alist = append(alist, &test)
    }
    fmt.Println(alist[0].Name, alist[1].Name)

打印结果:

ini 复制代码
addr=0x14000096230
addr=0x14000096230
b b

和预期的结果不符

每次遍历打印的内存地址都是相同,所以如果我们存放的是指针,本质上存储的都是同一块内存地址的内容,所以值相同。

如何想达到预期效果,我们只需将test赋值给一个临时变量即可,通过打印地址也可以发现临时变量temp的地址发生了变化。

css 复制代码
list2 := []Demo{{"a"}, {"b"}}
    var alist []*Demo
    for _, test := range list2 {
        temp := test
        fmt.Printf("addr=%p\n", &temp)
        alist = append(alist, &temp)
    }
    fmt.Println(alist[0].Name, alist[1].Name)

打印结果:

ini 复制代码
addr=0x14000010270
addr=0x14000010280
a b

更多具体类似的例子可以阅读该文章:github.com/golang/go/w...

for range与切片等元素修改

当我们在for range进行遍历的时候,如果我们对原先切片进行添加操作,后面再读取时并不会读取到最新添加的元素。

例如:

css 复制代码
var a = []int{1, 2, 3, 4, 5}
    var r = make([]int, 0)
    for i, v := range a {
        if i == 0 {
            a = append(a, 6, 7)
        }
        r = append(r, v)
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)

打印结果:

ini 复制代码
r =  [1 2 3 4 5]
a =  [1 2 3 4 5 6 7]

从上面可以看出并没有将新添加的元素 "6,7"添加到新的切片r中。

因为循环遍历中的切片a只是原切片的一个副本。

相关推荐
Java陈序员16 小时前
代码检测器!一款专门揭露屎山代码的质量分析工具!
docker·go
豆浆Whisky16 小时前
Go编译器优化秘籍:性能提升的黄金参数详解|Go语言进阶(16)
后端·go
不爱笑的良田16 小时前
从零开始的云原生之旅(九):云原生的核心优势:自动弹性伸缩实战
云原生·容器·kubernetes·go
无限中终2 天前
ENERGY Designer:重构跨平台GUI开发的高效解决方案
重构·go·结对编程
shining2 天前
[Golang] 万字详解,深入剖析context
go
一语长情2 天前
Go高并发背后的功臣:Goroutine调度器详解
后端·架构·go
代码扳手3 天前
Go 开发的“热更新”真相:从 fresh 到真正的零停机思考
后端·go
cr7xin3 天前
缓存查询逻辑及问题解决
数据库·redis·后端·缓存·go
ljq4 天前
Go:interface原理详解-接口由使用者定义,而不是由实现者定义。接口的常见疑惑
go
半枫荷4 天前
十二、Go语法进阶(接口和泛型)
go