Go 语言常见踩坑记

开发 后端
For循环在我们日常编码中可能用的很多。在很多业务场景中我们都需要用for循环处理。但golang中的for循环在使用上需要注意一些问题,大家可否遇到。

引言

本系列会列举一些在Go面试中常见的问题。

切片循环问题

For循环在我们日常编码中可能用的很多。在很多业务场景中我们都需要用for循环处理。但golang中的for循环在使用上需要注意一些问题,大家可否遇到。先看下边这一段代码:

func testSlice() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        go func() { 
            fmt.Println(v) 
        }() 
    } 
     
    time.Sleep(time.Second

 
output: 3 3 3 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

那么为什么会输出的是这个结果呢?

在golang的for循环中,循环内部创建的函数变量都是共享同一块内存地址,for循环总是使用同一块内存去接收循环中的的value变量的值。不管循环多少次,value的内存地址都是相同的。我们可以测试一下:

func testSliceWithAddress() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        go func() { 
            fmt.Println(&v) 
        }() 
 
    } 
 
    time.Sleep(time.Second

 
output
        0xc0000b2008 
        0xc0000b2008 
        0xc0000b2008 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

符合预期。如果大家比较感兴趣的话可以去将这段代码的汇编打印出来,就可以发现循环的v一直在操作同一块内存。

同样的,在slice循环这块我们还会遇见另一个有趣的地方,大家可以看看下边这段代码输出什么?

func testRange3() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        a = append(a, v) 
    } 
 
    fmt.Println(a) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

这段代码的输出结果是:[1 2 3 1 2 3],为什么呢?因为golang在循环前会先拷贝一个a,然后对新拷贝的a进行操作,所以循环的次数不会随着append而增多。

interface和nil比较

比如返回了一个空指针,但并不是一个空interface

func testInterface() { 
    doit := func(arg int) interface{} { 
        var result * struct{} = nil 
        if arg > 0 { 
            result = &struct{}{} 
        } 
 
        return result 
    } 
 
    if res := doit(-1); res != nil { 
        fmt.Println("result:", res) 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

输出结果为:result: ,为什么呢?因为在go里边变量有类型和值两个属性,在比较的时候也会比较类型和值都相同才会认为相等。代码中result的类型是指针,值是nil,所以会有这样的输出。

可变参数是空接口类型

当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。

func testVolatile() { 
    var a = []interface{}{1, 2, 3} 
 
    fmt.Println(a) 
    fmt.Println(a...) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

输出结果为:

[1 2 3] 
1 2 3 
  • 1.
  • 2.

map遍历时顺序不固定

不要相信map的顺序!

func testMap() { 
    m := map[string]string{ 
        "a""a"
        "b""b"
        "c""c"
    } 
 
    for k, v := range m { 
        println(k, v) 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

具体原因大家可以看一下源码:map.go:mapiterinit,就会发现下边这个代码用来决定从哪开始遍历map。另一个原因是map 在某些特定情况下(例如扩容),会发生key的搬迁重组。而遍历的过程,就是按顺序遍历bucket,同时按顺序遍历bucket中的key。搬迁后,key的位置发生了重大的变化,所以遍历map的结果就不可能按原来的顺序了。

func mapiterinit(t *maptype, h *hmap, it *hiter) { 
        ...... 
    // decide where to start 
    r := uintptr(fastrand()) 
        ...... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

 

责任编辑:武晓燕 来源: NoSay
相关推荐

2020-09-15 08:46:26

Kubernetes探针服务端

2017-05-05 08:12:51

Spark共享变量

2021-09-03 11:15:18

场景sql配置

2022-01-07 11:48:59

RabbitMQGolang 项目

2024-04-01 08:05:27

Go开发Java

2015-09-07 10:15:53

移动端开发

2023-03-06 07:50:19

内存回收Go

2023-03-13 13:36:00

Go扩容切片

2022-07-31 23:05:55

Go语言短变量

2023-01-18 23:20:25

编程开发

2022-11-01 18:29:25

Go语言排序算法

2021-09-07 15:16:04

鸿蒙HarmonyOS应用

2024-01-06 08:16:19

init​函数数据开发者

2022-08-08 06:50:06

Go语言闭包

2024-01-04 07:49:00

Go语言方法

2023-02-20 08:11:04

2022-01-03 20:13:08

Gointerface 面试

2022-08-08 08:31:55

Go 语言闭包匿名函数

2024-04-10 08:39:56

BigDecimal浮点数二进制

2024-01-12 07:16:30

Go语言运行
点赞
收藏

51CTO技术栈公众号