Go语言中for循环的经典案例分享

本文详细分析了Go语言中for循环中的四个常见陷阱:传值与传址操作导致的值共享,闭包中循环变量值的变化,以及在for循环中启动goroutine可能导致的结果。通过实例演示和解决方案,帮助开发者避免这些潜在问题。
摘要由CSDN通过智能技术生成

for循环问题,在面试中经常都会被问到,并且在实际业务项目中也经常用到for循环,要是没用好,一不下心就掉坑。

下面会挑选几个经典的案例,一块来探讨下,看看如何避免掉坑,多积累积累采坑经验。

案例一:for+传值

先来到开胃菜,热热身~

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

type student struct {

  name string

  age  int

}

func main() {

  m := make(map[string]student)

  stus := []student{

    {name: "张三", age: 18},

    {name: "李四", age: 23},

    {name: "王五", age: 26},

  }

  for _, stu := range stus {

    m[stu.name] = stu

  }

  for k, v := range m {

    fmt.Println(k, "=>", v.name)

  }

}

不出意料,输出结果为

李四 => 李四
王五 => 王五
张三 => 张三

这题比较简单,就是简单的传值操作,大家应该都能答上来。下面加大难度,改为传址操作

案例二:for+传址

将案例一改为传址操作

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

type student struct {

  name string

  age  int

}

func main() {

  m := make(map[string]*student)

  stus := []student{

    {name: "张三", age: 18},

    {name: "李四", age: 23},

    {name: "王五", age: 26},

  }

  for _, stu := range stus {

    m[stu.name] = &stu

  }

  for k, v := range m {

    fmt.Println(k, "=>", v.name)

  }

}

好好想想应该输出什么结果呢?还是跟案例一是一样的结果吗?难道会有坑?

不出意料,还是出了意外,输出结果为

张三 => 王五
李四 => 王五
王五 => 王五

为什么呢?

  • 首先,关键点在于Go的for循环,循环变量stu每次是循环并不是迭代(简单的说,就是对循环变量stu只会做一次声明和内存地址的分配,后面循环就是不断更新值);
  • 所以,取址操作 &stu,其实都是取的同一个变量的地址,只是值被循环更新为最后一个元素的值;
  • 最终,输出的v.name,都是最后一个元素的name为王五

解决方案

在for循环中,做同名变量覆盖stu:=stu(即重新声明一个局部变量,做值拷贝,避免相互影响)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

type student struct {

  name string

  age  int

}

func main() {

  m := make(map[string]*student)

  stus := []student{

    {name: "张三", age: 18},

    {name: "李四", age: 23},

    {name: "王五", age: 26},

  }

  for _, stu := range stus {

    stu := stu  //同名变量覆盖

    m[stu.name] = &stu

  }

  for k, v := range m {

    fmt.Println(k, "=>", v.name)

  }

}

输出结果:

张三 => 张三
李四 => 李四
王五 => 王五

案例三:for+闭包

在for循环里,做闭包操作,也是很容易掉坑的。看看下面输出什么?

1

2

3

4

5

6

7

var prints []func()

for _, v := range []int{1, 2, 3} {

  prints = append(prints, func() { fmt.Println(v) })

}

for _, print := range prints {

  print()

}

一眼看过去,感觉是输出1 2 3,但实际会输出 3 3 3

为什么呢?

  • 首先,在分析了案例二后,我们知道了Go的for循环对循环变量v,其实每次是循环并不是迭代;
  • 然后,闭包=函数+引用环境,在同一个引用环境下,循环变量v的值会被不断的覆盖;
  • 所以最终,在打印时,输出的v,都是最后一个值3。

解决方案

和案例二解决方案一样,是在for循环中,做同名变量覆盖v:=v

1

2

3

4

5

6

7

8

var prints []func()

for _, v := range []int{1, 2, 3} {

  v := v //同名变量覆盖 

  prints = append(prints, func() { fmt.Println(v) })

}

for _, print := range prints {

  print()

}

输出结果:

1
2
3

案例四:for+goroutine

在for循环里,起goroutine协程,也是很迷惑很容易掉坑的。看看下面输出什么?

1

2

3

4

5

6

7

8

9

10

var wg sync.WaitGroup

strs := []string{"1", "2", "3", "4", "5"}

for _, str := range strs {

  wg.Add(1)

  go func() {

    defer wg.Done()

    fmt.Println(str)

  }()

}

wg.Wait()

一眼看过去,感觉是会无序输出1 2 3 4 5,但实际会输出 5 5 5 5 5

为什么呢?

  • 首先,要记得Go的for循环对循环变量str,其实每次是循环并不是迭代;
  • 然后,main协程会和新起的协程做相互博弈,看谁执行更快,按这个案例执行情况来看,main协程执行速度明显比新起的协程会更快,所以str被更新为最后一个元素值5(备注:并非绝对);
  • 最终,在新起的协程中,使用str时值都为5,作为结果去输出;
  • 拓展:如果在新起协程前,sleep个5s,输出结果又会截然不同,感兴趣的同学可以自行实验下,然后逐步深入地了解下GMP调度机制

解决方案

和前面两个案例解决方案一样,是在for循环中,做同名变量覆盖str:=str

1

2

3

4

5

6

7

8

9

10

11

var wg sync.WaitGroup

strs := []string{"1", "2", "3", "4", "5"}

for _, str := range strs {

  str := str //同名变量覆盖

  wg.Add(1)

  go func() {

    defer wg.Done()

    fmt.Println(str)

  }()

}

wg.Wait()

输出结果:

5
4
2
1
3

注意是1~5无序输出

总结

for循环中做传址、闭包、goroutine相关操作,千万要注意,一不小心就会很容易掉坑。

到此这篇关于Go语言中for循环的经典案例分析的文章就介绍到这了,希望可以帮到你!

转自:微点阅读    https://www.weidianyuedu.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值