Go Range内幕


前言:翻译一篇国外小哥对Range的分析...

原文链接:
go-range loop internals

我们都知道Range使用非常方便,但是我总是可以发现Range有点神秘。而且并不是我一个人这么认为(me too):

  # 这个程序会无限循环吗?
  func main() {
    v := []int{1, 2, 3}
    for i := range v {
      v = append(v, i)
    }
  }
  — Dαve Cheney (@davecheney) January 13, 2017

现在我准备根据这些事实来探索Range中间发生了什么,并且会分步骤将这些都记录下来。

Step 1:

第一个任务就是读go语言规范文档。for语句部分 “For statements with rangeclause” 下的范围循环。
我这里不会复制整个文档,但是会记录其中有趣的内容。

首先,让我们提醒自己,我们在这里看到什么:

for i := range a {
    fmt.Println(i)
}
  • range 变量
    大多数人都知道在range子句的左边(在上面的例子中的i)你可以使用以下命令分配循环变量:
    -- 赋值(=)
    -- 短变量声明(:=)
    你也可以选择不做任何事情来完全忽略循环变量。
    如果使用短变量声明(:=),Go将为循环的每次迭代重用变量(仅在循环的范围内)
  • range 表达式
    在range子句的右侧(在上面的示例中a),你可以找到他们称之为range表达式的内容。它可以包含任何计算结果为以下之一的表达式:
    -- array
    -- pointer to an array
    -- slice
    -- string
    -- map
    -- channels that allow receiving, e.g. chan int or chan<- int
    在开始循环之前,range表达式被计算一次。
    ⚠️这里有一个例外:如果表达式是一个数组(或指针),那么只会len(a)对其进行求值。只对len(a)求值意味着表达式a可以在编译时进行求值,并有编译器替换为常量。len语言规范
    The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (non-constant) function calls; in this case s is not evaluated. Otherwise, invocations of len and cap are not constant and s is evaluated.
    文档中“evaluated”说的不是很清楚,在其他规范中也找不到相关信息。当然我可以猜测它完全执行表达式,知道它无法进一步减少。在任何情况下,这里的高阶位是在循环开始之前对表达式进行了一次求值。
    那么你如何对表达式进行求值?通过将其赋值为变量!这就是这里发生的事吗?
    有趣的是,Go语言规范文档中有关于添加、删除map描述:(没有slices)
    If map entries that have not yet been reached are removed during iteration, the corresponding iteration values will not be produced. If map entries are created during iteration, that entry may be produced during the iteration or may be skipped.
    稍后我们将回到map上。

Step 2:range 支持数据类型

如果我们假设在循环开始之前被赋值给变量一次,这意味着什么?答案是取决于变量类型,所以让我们仔细看看range支持的数据类型。
在这样做之前,请记住:in Go, everything you assign, you copy.(值传递)如下表:

数据类型 语法糖
array the array
string struct holding len + a pointer to the backing array
slice struct holding len, cap + a pointer to the backing array
map pointer to a struct
channel pointer to a struct

请参阅本文底部的参考资料,以了解有关这些数据类型的内部结构的更多信息。
那么这是什么意思?看下面的例子不同之处:

// copies the entire array
var a [10]int
acopy := a 

// copies the slice header struct only, NOT the backing array
s := make([]int, 10)
scopy := s

// copies the map pointer only
m := make(map[string]int)
mcopy := m

因此,如果在range循环开始时将数组表达式赋给变量(以确保它只计算一次),那么你将复制整个数组。我们可能会在这里做点什么。

Step 3: Go源码

在Go源码中,这个文件 statements.cc,就for而言正如注释所述:

// Arrange to do a loop appropriate for the type.  We will produce
//   for INIT ; COND ; POST {
//           ITER_INIT
//           INDEX = INDEX_TEMP
//           VALUE = VALUE_TEMP // If there is a value
//           original statements
//   }

range循环内部只是c风格循环的语法糖。例如:
Array

// The loop we generate:
//   len_temp := len(range)
//   range_temp := range
//   for index_temp = 0; index_temp < len_temp; index_temp++ {
//           value_temp = range_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }

Slice

//   for_temp := range
//   len_temp := len(for_temp)
//   for index_temp = 0; index_temp < len_temp; index_temp++ {
//           value_temp = for_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }

这里有一些公共点:

  • 这都是c风格循环
  • 迭代时被分配给一个临时变量

我们所知道的

  • 1.循环变量在每次迭代时被重用并分配给它们。
  • 2.通过赋值给变量,在循环开始之前对范围表达式求值一次。
  • 3.你可以在迭代时添加或删除map的值,循环中可能会显示添加内容,也可能不会显示。

回到前面

func main() {
    v := []int{1, 2, 3}
    for i := range v {
      v = append(v, i)
    }
  }

这个程序会被终止原因是因为它被翻译成了下面的代码:

for_temp := v
len_temp := len(for_temp)
for index_temp = 0; index_temp < len_temp; index_temp++ {
        value_temp = for_temp[index_temp]
        index = index_temp
        value = value_temp
        v = append(v, index)
}

我们知道slice是一个包含指向底层数组的指针的结构的语法糖。循环迭代len_temp,这是在循环开始之前就获取到的结构副本。因此,变量本身的更改并不相关,因为它是结构的另外一个副本。支持的数组仍然是共享的,因为它只是该结构中的指针,所以类似 v[i] = 1 还是可以的。

其他:map

在Go语言规范中,我们知道:

  • 在循环中添加或删除map是安全的
  • 如果你添加一个map元素,它可能会或者不会在下面的循环中看到它

它为什么这么工作?首先,我们知道map是指向struct的指针。在循环开始之前,将复制指针而不是内部数据结构,因此可以在循环中添加或删除键。

那为什么在下面的循环中看不到添加的map元素内?好吧,如果你知道哈希表是如何工作的,map就是如此,在哈希表的支持数组中,元素没有特定顺序。你最后添加的元素可能会在数组中散列为索引零。因此,如果您假设Go保留以任何顺序迭代此数组的权利,则确实无法预测您是否会在循环内看到您添加的项目。毕竟,您可能已经在支持数组中超过索引零​​。这可能与Go映射的情况不完全相同,但出于这个原因将决策留给编译器编写器是有意义的。

总结

步骤清晰,思路明确,还是引申扩展,写的很好,对于新手来说这也是一个容易犯错的地方。

string source code
slice source code
map source code
channel source code

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

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,308评论 1 47
  • 一切都在变, 唯一不变就是变, 一切都没有变, 是的, 变与不变,好像是一个好命题。
    冯军宏阅读 260评论 0 0
  • 文/雪诺 公众号:迷茫人生路 最近被一只旅行的青蛙给刷爆了朋友圈,这款游戏用当下流行的词语来形容就是:“佛系...
    Snow凤阅读 330评论 0 0
  • 本文仅代表个人观点,图片来源于网络 与其说我是翻墙过来的还不如说我是跋山涉水过来的。 第一天到公司上班,就碰上了爱...
    箬之曦阅读 453评论 0 2