Go-for range

Go中 有关循环,有两种,一种是for i :=0;i < len(x); i++ 的经典模式,另外一种是for k, v := range xxx。在用第二种方式时,有一些坑,这儿简单总结一下,希望对跟我一样有疑惑的朋友 有所帮助~

1 for range 支持的数据类型

for range 目前支持slice、map、string以及channel。 在每一种的使用过程中编译器都会对其做转换。接着往下看

2 for range 应用与原理

2.1 slice 与 array

为了方便讲解,下文的 默认操作例子 for k, v := range a {}

slice 跟 array 的for range 操作,会被编译器转换成经典for循环模式。
编译之后,大体是这样的:

ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
for ; hv1 < hn; hv1++ {
    // ...
}

在转换之前,首先是进行了slice 或者array进行了拷贝,这样在实际遍历过程中,对v 的操作就不是对原slice/array操作了。如果需要影响原slice的内容,则需要操作&a[k] 了。

举个例子直观看一下:

func main() {
  a := []int{1,2,3}
    for _, v := range a {
  // 这儿的v 是 a的副本的value值。所以这样不会改变a的值
        v++
    }
    fmt.Println(a) // [1 2 3]
    // 如果想改变原来数组中的内容,其实可以直接操作原slice 地址
    for k, v := range a {
        a[k] = v + 1
    }
    fmt.Println(a) // [2 3 4]
}

再来个例子:

func main() {
    a := []int{1,2,3}
    for _, v := range a {
    // v 是a副本的值,所以只有3个哦~
        a = append(a, v)
    }
    fmt.Println(a) // 1 2 3 1 2 3
}

2.2 map

2.2.1 map for range 的无序性

map 的 for range 操作,编译之后会被展开为 mapiterinitmapiternext
编译之后,大体是这样的:

ha := a
hit := hiter(n.Type)
th := hit.Type
mapiterinit(typename(t), ha, &hit)
for ; hit.key != nil; mapiternext(&hit) {
    key := *hit.key
    val := *hit.val
}

mapiterinit 的主要用途是:

随机找一个桶作为遍历的开始(这也是为什么每次for range同一个map结果不一样的原因

mapiternext 的主要用途是:

  • 遍历桶中的数据
  • 当前桶中数据遍历完毕,寻找下一个桶
  • 所有桶都遍历完毕,则结束。

注意:每次for range map ,都会产生不同的顺序。

2.2.2 map for range 过程中添加、删除元素

我们再看一个 写了个例子:

func main() {
  amap := map[string]int{"zhangpeng":3, "chris": 1, "lmm" : 2}
    for k, v := range amap {
        fmt.Println(k)
        //time.Sleep(time.Microsecond * 50)
        k = k + "-1"
        amap[k] = v
    }
    fmt.Println(amap)
}

发现每次执行的结论是不一样的。
这是为什么呢?

  • 如果 map 中的元素在还没有被遍历到时就被移除了,后续的迭代中这个元素就不会再出现
  • 如果 map 中的元素是在迭代过程中被添加的,那么在后续的迭代这个元素可能出现也可能被跳过。

2.3 string

字符串的遍历与数组和哈希表非常相似,只是在遍历的过程中会获取字符串中索引对应的字节,然后将字节转换成 rune,我们在遍历字符串时拿到的值都是 rune 类型的变量,其实类似 for i, r := range s {} 的结构都会被转换成如下的形式:

ha := s
for hv1 := 0; hv1 < len(ha); {
    hv1t := hv1
    hv2 := rune(ha[hv1])
    if hv2 < utf8.RuneSelf {
        hv1++
    } else {
        hv2, hv1 = decoderune(h1, hv1)
    }
    v1, v2 = hv1t, hv2
}

如果当前的 rune 是 ASCII 的,那么只会占用一个字节长度,这时只需要将索引加一,但是如果当前的 rune 占用了多个字节就会使用 decoderune 进行解码。

举个例子:

func main() {
  astr := "hello 中国"
    for k, v := range astr {
        fmt.Println(k, v, string(v))
    }
}

结果是:

0 104 h
1 101 e
2 108 l
3 108 l
4 111 o
5 32  
6 20013 中
9 22269 国

如果使用经典的for 循环,遍历string, 就会出现乱码问题了。

func main() {
  astr := "hello 中国"
    for i := 0; i < len(astr); i++ {
        fmt.Println(i, astr[i], string(astr[i]))
    }
}

结论是这样的:

0 104 h
1 101 e
2 108 l
3 108 l
4 111 o
5 32  
6 228 ä
7 184 ¸
8 173 ­
9 229 å
10 155 
11 189 ½

2.4 channel

for v := range ch {} 编译之后大体是这样的:

ha := a
hv1, hb := <-ha
for ; hb != false; hv1, hb = <-ha {
    v1 := hv1
    hv1 = nil
    // ...
}

循环会使用 <-ch 从管道中取出等待处理的值,这个操作会调用 chanrecv2 并阻塞当前的协程,当 chanrecv2 返回时会根据 hb 来判断当前的值是否存在,如果不存在就意味着当前的管道已经被关闭了,在正常情况下都会为 v1 赋值并清除 hv1 中的数据,然后会陷入下一次的阻塞等待接受新的数据。

这部分目前还没有遇到坑,简单举个例子吧

func main() {
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    //close(queue)
    for elem := range queue {
        fmt.Println(elem)
    }
}

3 小结

本文总结了for range 在Go中的使用,以及一些遇到的坑。希望对你有所帮助~

4 参考文献

知其然,知其所以然 https://draveness.me/golang/keyword/golang-for-range.html
map在循环过程中添加、删除元素会发生什么?https://www.jianshu.com/p/35c2662b5b57

5 其他

本文是《循序渐进go语言》的第十篇-《Go-for range》。
如果有疑问,可以直接留言,也可以关注公众号 “链人成长chainerup” 提问留言,或者加入知识星球“链人成长” 与我深度链接~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容