Go 语言 for-range 的两个坑,你踩过吗?
导读:坑一:迭代时协程引用索引和值先看看下面的例子,你知道最终输出的结果是什么吗?package main import ( "fmt" "time" func main( { var m = []int{1, 3, 5} fo...
坑一:迭代时协程引用索引和值
先看看下面的例子,你知道最终输出的结果是什么吗?
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 都是输出了最后迭代的索引和值。
我觉得,理解清楚以下两点就可以了:
- 闭包内引用了外部变量 i 和 v,三个协程都引用了
- 协程运行时,循环可能已经结束了
要想解决这个问题,可以改成闭包传参的形式:
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/
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Go 语言 for-range 的两个坑,你踩过吗?
本文地址: https://pptw.com/jishu/504620.html