Golang开发易错点

  • A+
所属分类:编程开发

总结Golang开发时容易掉的坑

make和append同时用的问题

golang支持切片,可以append追加元素,十分方便。
make是用于初始化切片的函数,但如果对其没有理解清楚,就容易出错。

错误示例

这是一个初始化切片的代码。

package main

import "fmt"

func main() {
    values:=make([]int,3)
    values=append(values,1)
    fmt.Println(values)  // 输出 [0 0 0 1]
}

因为make后的切片已经初始化了长度为3,此时再append会追加到后面,就多此一举了。
因此,在Golang里直接初始化空切片更方便。

正确写法

values:=[]int{}
values=append(values,1)

无法更新的数组

错误示例

package main
import "fmt"

func main() {
    data := []int{1, 2, 3}
    for _, v := range data {
        v += 1 //原始元素未更改
    }
    fmt.Println(data) //输出 [1 2 3]
}

我们会发现,数组元素并未更改。
这是因为for循环中的v,是一个元素的拷贝。
如果需要修改,应当使用下标,或使用指针数组

正确写法

package main
import "fmt"

func main() {
    data := []int{1, 2, 3}
    for i := range data {
        data[i] += 1
    }
    fmt.Println(, data) //输出 [10 20 30]
}

并发问题

错误示例

下面的代码应该是希望将数组下标并行打印。

package main

import "fmt"
import "time"

func main() {
    values:=[]int{1,2,3,4,5}
    for idx := range values {
        go func() {
            fmt.Println(idx)
        }()
    }
    time.Sleep(3*time.Second)
}

但我们发现程序并没有和想象的那样输出序列,而是输出了4。这是因为协程并没有立刻执行,而在执行的时候,idx已经被赋值成了4

正确写法

val 作为一个参数传入 goroutine 中,每个 val 都会被独立计算并保存到 goroutine 的栈中,从而得到预期的结果。

package main

import "fmt"
import "time"

func main() {
    values:=[]int{0,1,2,3,4}
    for idx := range values {
        go func(idx int) {
            fmt.Println(idx)
        }(idx)
    }
    time.Sleep(3*time.Second)
}

此外,在循环里,将参数赋值一次,也可以达到同样效果。但从程序美观的角度考虑,建议使用上面的方式。

package main

import "fmt"
import "time"

func main() {
    values:=[]int{0,1,2,3,4}
    for idx := range values {
        x:=idx
        go func() {
            fmt.Println(x)
        }()
    }
    time.Sleep(3*time.Second)
}

没有被捕获的error

通常,为了捕获错误,然后进行处理,我们会在函数中增加一个defer

错误示例

下面的代码中,defer并未捕获到error

package main

import "fmt"

func main() {
    var err error
    defer fmt.Printf("error is [%v]\n",err)
    err = fmt.Errorf("test error")
    fmt.Println(err)
}

输出

test error
error is []

这是因为defer后跟的函数的传参是复制传参,而这个动作在执行到defer这条语句时就做了,并不是在函数结束时处理的。
这和并发时的闭包问题类似,但这里我们希望它拿到最后的error信息。因此需要这样处理。

正确写法

package main

import "fmt"

func main() {
    var err error
    defer func(){
        fmt.Printf("error is [%v]\n",err)
    }()
    err = fmt.Errorf("test error")
    fmt.Println(err)
}

没有执行完的协程

Golang提供了非常方便的并发开发,使用go语句,就可以开启一个协程。然后可以通过WaitGroup来控制并发等待。

错误示例

下面的代码似乎想并行输出数组下标,并且做了协程等待。
但实际上这个程序却没有输出任何内容(也可能会输出,存在偶然情况),这是为什么呢?

package main

import "fmt"
import "sync"

func main() {
    wg:=&sync.WaitGroup{}
    values:=[]int{1,2,3,4,5}
    for i := range values {
        // wg.Add(1) 正确的位置
        go func(i int,wg *sync.WaitGroup){
           wg.Add(1)   // 错误的位置
           defer wg.Done()
           fmt.Println(i)
        }(i,wg)
    }
    wg.Wait()
}

代码里,最大的问题是wg.Add的位置,它不应该放到协程中。协程不一定是立刻执行的,因此这可能会导致wg.Wait先执行,直接就退出了。正确的写法应该将其放到go语句的前面。
而如果运气好的话,也可能会输出部分内容。

  • 版权声明:本站原创文章,于2024年2月28日21:47:00,由 发表,共 2188 字,未经允许请勿转载。
  • 本文固定链接:Golang开发易错点 | x64

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: