go常用函数之range

range关键字是Go语言中一个非常有用的迭代array,slice,map, string, channel中元素的内置关键字。

range的使用

range的使用非常简单,对于遍历array,*array,string它返回两个值分别是数据的索引和值,遍历map时返回的两个值分别是key和value,遍历channel时,则只有一个返回数据。各种类型的返回值参考下表:

range expression 1st Value 2nd Value(optional) notes
array[n]E,*[n]E indexint value E[i]
slice []E indexint value E[i]
string abcd indexint rune int 对于stringrange迭代的是Unicode而不是字节,所以返回的值是rune
mapmap[k]v keyk value v
channel element none 遍历channel时,则只有一个返回数据

使用方式非常简单,下面直接贴一段gobyexample中的示例代码作为参考,执行结果请点击链接跳转到gobyexample

package main
import "fmt"
func main() {

    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)

    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }

    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }

    for k := range kvs {
        fmt.Println("key:", k)
    }

    for i, c := range "go" {
        fmt.Println(i, c)
    }
}

range的详细说明也请跳转官方文档的for_statement章节自行阅读。本文主要介绍一些平时可(yi)能(bu)被(xiao)忽(xin)略(jiu)的(diao)细(keng)节(li)。

range表达式构建

先来看看下面这段代码的输出是什么?这段代码会无限循环的执行下去吗?

func modifySlice() {
    v := []int{1, 2, 3, 4}
    for i := range v {
        v = append(v, i)
        fmt.Printf("Modify Slice: value:%v\n", v)
    }
}

答案肯定不会无限循环的,这么低级的错误,Go的开发者肯定是不会范的。那结果会是什么呢?执行这段代码会打印下面的内容:

Modify Slice: value:[1 2 3 4 0]
Modify Slice: value:[1 2 3 4 0 1]
Modify Slice: value:[1 2 3 4 0 1 2]
Modify Slice: value:[1 2 3 4 0 1 2 3]

可以看到每次循环在map中插入新的内容后,map的长度确实发生了变化,但是循环只执行了四次,正好是执行range前map的长度。说明range在执行之初就构建好了range表达式的内容了,虽然后面map的内容增加了,但是并不会影响初始构建的结果。官方文档对于range表达式的构建描述是这样的:

The range expression x is evaluated once before beginning the loop, with one exception: if at most one iteration variable is present and len(x) is constant, the range expression is not evaluated.
就是说range表达式在开始执行循环之前就已经构建了,但是有一个例外就是:如果最多只有一个迭代变量,且len(x)表达的是值是一个常量的时候range表达式不会构建。那什么时候len(x)是一个常量呢?按照通常的理解,len(string), len(slice), len(array)…的返回应该都是常量啊?事实上不是这样的,这其实是由数据结构的特性决定的。因为相比较于其他语言,对于这一类容器数据结构,在Go语言中不仅有长度属性可以通过内建函数len()获取,还有一个可以通过内建函数cap()获取的容量属性,尤其是slice,map这一类可变长的数据结构。所以对于常量的定义,Go官方文档Length and capacity有详细的说明。

如果到这里,你以为你已经理解了range的构建,并且一眼就能看出一个for-range循环的迭代方式和执行情况。前面可能就已经有一个大坑为你准备好了。这时候官方文档里面下面这样一段话可能就被你忽略了,让我先贴出来:

The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced. If a map entry is created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.
用中国话解释下,首先对于map的迭代来说,map中内容的迭代顺序没有指定,也不保证,简单的说就是迭代map的时候将以随机的顺序返回里面的内容。这个好理解,也就是说如果你想按顺序迭代一些东西,map就不要指望了,换其他数据结构吧。
然后就进入高潮部分了,如果一个map的key在还没有被迭代到的时候就被delete掉了,那么它所对应的迭代值就不会被产生了。然后对于在迭代过程中新增加的key,则它可能会被迭代到也可能不会。如何选择会根据key增加的时机以及从上一次到下一次的迭代而不同。你可能会想,What?你TM在逗我么,说好的提前构建的呢…但是很不幸,事实就是这样,将前面的示例代码改成使用map我执行了几次结果都不一样。所以这种坑还是绕过为好。至于为什么会这样,容我有空再研究研究,下次重开一篇文章介绍。

示例代码:

func modifyMap() {
    data := map[string]string{"a": "A", "b": "B", "c": "C"}
    for k, v := range data {
        data[v] = k
        fmt.Println("modify Mapping", data)
    }
}

结果1,迭代了6次

modify Mapping map[a:A b:B c:C A:a]
modify Mapping map[b:B c:C A:a B:b a:A]
modify Mapping map[c:C A:a B:b C:c a:A b:B]
modify Mapping map[a:A b:B c:C A:a B:b C:c]
modify Mapping map[A:a B:b C:c a:A b:B c:C]
modify Mapping map[a:A b:B c:C A:a B:b C:c]

结果2,迭代了4次

modify Mapping map[a:A b:B c:C A:a]
modify Mapping map[b:B c:C A:a B:b a:A]
modify Mapping map[a:A b:B c:C A:a B:b C:c]
modify Mapping map[B:b C:c a:A b:B c:C A:a]

range string

使用range迭代字符串时,需要主要的是range迭代的是Unicode而不是字节。返回的两个值,第一个是被迭代的字符的UTF-8编码的第一个字节在字符串中的索引,第二个值的为对应的字符且类型为rune(实际就是表示unicode值的整形数据)。

总结下来就是使用range迭代string时,需要注意下面两点:

  • 迭代的是Unicode不是字节,第一个返回值是UTF-8编码第一个字节的索引,所以索引值可能并不是连续的。
  • 第二个返回值的类型为rune,不是string类型的,如果要使用该值需要格式化。

看下面代码:

//string
func rangeString() {
    datas := "aAbB"

    for k, d := range datas {
        fmt.Printf("k_addr:%p, k_value:%v\nd_addr:%p, d_value:%v\n----\n", &k, k, &d, d)
    }
}

这段代码的输出如下,这里使用的string是只占用一个字节的字符的字符串,所以返回的第一个索引是连续的,可以看到第二个值都是整型数字。

k_addr:0xc420014148, k_value:0
d_addr:0xc420014150, d_value:97
k_addr:0xc420014148, k_value:1
d_addr:0xc420014150, d_value:65
k_addr:0xc420014148, k_value:2
d_addr:0xc420014150, d_value:98
k_addr:0xc420014148, k_value:3
d_addr:0xc420014150, d_value:66

range可以对string做更多的事情

前面说到range是对Unicode进行迭代来迭代字符串的,所以range还能够在迭代过程中发现字符串中非Unicode的字符,并使用U+FFFD字符替换改无效字符。
看下面代码的执行:

func rangeStringMore() {
    for pos, char := range "中\x80文" { // \x80 is an illegal UTF-8 encoding
        fmt.Printf("character %#U starts at byte position %d\n", char, pos)
    }
}

输出

character U+4E2D '中' starts at byte position 0
character U+FFFD '�' starts at byte position 3
character U+6587 '文' starts at byte position 4

上面这段代码使用range迭代字符串"中\x80文",字符串中\x80是一个无效的的Unicode字符,所以range在迭代时会使用U+FFFD将其替换。另外UTF-8使用变长方式编码,第一个汉字中占用了3个字节,所以遍历第二个字符的时候,其索引已经是3了,但是其只占一个字节,所以第三个字符文的索引4.

range表达式是指针

前面说过range可以迭代的数据类型包括array,slice,map,string和channel。在这些数据类型里面只有array类型能以指针的形式出现在range表达式中。

具体参考下面的代码:

func rangePointer() {
    //compile error: cannot range over datas (type *string)
    //d := "aAbBcCdD"
    d := [5]int{1, 2, 3, 4, 5} //range successfully
    //d := []int{1, 2, 3, 4, 5} //compile error: cannot range over datas (type *[]int)
    //d := make(map[string]int) //compile error: cannot range over datas (type *map[string]int)
    datas := &d

    for k, d := range datas {
        fmt.Printf("k_addr:%p, k_value:%v\nd_addr:%p, d_value:%v\n----\n", &k, k, &d, d)
    }
}

文章转载至:https://blog.csdn.net/xingwangc2014/article/details/79777724

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

推荐阅读更多精彩内容