企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## range实现原理 对于for-range语句的实现,可以从编译器源码中找到答案。 编译器源码`gofrontend/go/statements.cc/For_range_statement::do_lower()`方法中有如下注释。 ~~~go // 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风格的循环结构。range支持数组、数组指针、切片、map和channel类型,对于不同类型有些细节上的差异 ## 1.1 range for slice 下面的注释解释了遍历slice的过程: ~~~go // The loop we generate: // 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 // } ~~~ 遍历slice前会先获取slice的长度len\_temp作为循环次数,循环体中,每次循环会先获取元素值,如果for-range中接收index和value的话,则会对index和value进行一次赋值。 由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是没办法遍历到的。 另外,数组与数组指针的遍历过程与slice基本一致,不再赘述。 ## 1.2 range for map 下面的注释解释了遍历map的过程: ~~~go // The loop we generate: // var hiter map_iteration_struct // for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) { // index_temp = *hiter.key // value_temp = *hiter.val // index = index_temp // value = value_temp // original body // } ~~~ 遍历map时没有指定循环次数,循环体与遍历slice类似。由于map底层实现与slice不同,map底层使用hash表实现,插入数据位置是随机的,所以遍历过程中新插入的数据不能保证遍历到。 ## 1.3 range for channel 遍历channel是最特殊的,这是由channel的实现机制决定的: ~~~go // The loop we generate: // for { // index_temp, ok_temp = <-range // if !ok_temp { // break // } // index = index_temp // original body // } ~~~ channel遍历是依次从channel中读取数据,读取前是不知道里面有多少个元素的。如果channel中没有元素,则会阻塞等待,如果channel已被关闭,则会解除阻塞并退出循环。 注: * 上述注释中index\_temp实际上描述是有误的,应该为value\_temp,因为index对于channel是没有意义的。 * 使用for-range遍历channel时只能获取一个返回值。