Go 语言 for-range 的两个坑,你踩过吗?

本文介绍了在使用 Go 语言的 for-range 迭代过程中可能会遇到的两个问题:一是协程引用索引和值可能导致意外结果,二是迭代时值为原始对象的拷贝而非引用。通过示例代码解释了这两个坑的原因,并提供了相应的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

坑一:迭代时协程引用索引和值

先看看下面的例子,你知道最终输出的结果是什么吗?

package main

import (
	"fmt"
	"time"
)

func main() {
	var m = []int{1, 3, 5}
	for i, v := range m {
		go func() {
			fmt.Println(i, v)
		}()
	}
	time.Sleep(time.Second)
}

不知道的同学,大家可以在 https://play.golang.org/ 这里尝试运行一下。

正确答案是:

2 5
2 5
2 5

这是为啥?三个 goroutine 都是输出了最后迭代的索引和值。

我觉得,理解清楚以下两点就可以了:

  1. 闭包内引用了外部变量 i 和 v,三个协程都引用了
  2. 协程运行时,循环可能已经结束了

要想解决这个问题,可以改成闭包传参的形式:

func main() {
	var m = []int{1, 3, 5}
	for i, v := range m {
		go func(_i, _v int) {
			fmt.Println(_i, _v)
		}(i, v)
	}
	time.Sleep(time.Second)
}

也可以让每一轮循环都用新的变量:

func main() {
	var m = []int{1, 3, 5}
	for i, v := range m {
		_i := i
		_v := v
		go func() {
			fmt.Println(_i, _v)
		}()
	}
	time.Sleep(time.Second)
}

运行结果就符合预期了:

0 1
1 3
2 5
坑二:迭代时值为原先迭代对象的拷贝
package main

import (
	"fmt"
)

func main() {
	var m = []int{1, 3, 5}
	for i, v := range m {
		if i == 1 {
			v = 2
		}
		fmt.Println(i, v)
	}
	fmt.Println(m)
}

这个输出是啥?

0 1
1 2 // 明明改成 2 了,
2 5
[1 3 5] // 这里还是 3 ?

那是因为,Go 会在 range 循环中自动为遍历的对象创造一个副本,可以理解为一个值拷贝,如果真的想修改原数组,你得这样写:

func main() {
	var m = []int{1, 3, 5}
	for i, v := range m {
		if i == 1 {
			m[i] = 2 // 改成 m[i]
		}
		fmt.Println(i, v)
	}
	fmt.Println(m)
}

程序输出是这样:

0 1
1 3 // 由于是值拷贝,所以改了原来的 m[i] 不影响 v 的值,m[i]=2,v=3.
2 5
[1 2 5] // 可以看到已经改成 2 了

也不算是什么大坑,理解了感觉和 PHP 的 foreach 语法差不多。


文章来源于本人博客,发布于 2019-06-16,原文链接:https://imlht.com/archives/187/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值