语法糖系列 go 中的 for range 源码实现之 slice

3,204 阅读2分钟
从编译器的源码来看 for range 的实现

用 go 语言编码写的最多的应该是 下面这个错误的处理逻辑了

if err != nil {
    // todo 
}

但是今天讲的不是这个欸, 对不起可能要让你们失望了, 对不起, 真的没让你们失望啊!

从编译器源码的角度看 for range

不知道大家有没有写过这样的代码

package main

import (
    "fmt"
)
func main() {
    sl := []int{1,2,3,4}
    
    for _, v := range sl{
        sl = append(sl, v)
    } // ? 这个循环会结束么?
    
    fmt.Println(sl)
}

如果不确定 fmt.Println() 是否会输出, 恭喜你, 今天又可以收获一波干货, 继续往下看吧 🤦, 肯定不会让你失望的, 👍。

for range 是什么?

嗯, for range 只是 go 提供给我们的一个语法糖, 看起来简洁, 写起来舒服, 好像就是这个样子的

先来看看 range 可以接受什么类型的表达式?

  • 数组
  • 指向数组的指针
  • 切片
  • 字符串
  • map
  • 可以接受值的 channel, 像 <-chan E 这种

图片来自 go 的官网

go 语言中的赋值语句都是赋值

  • 如果赋值的是一个指针, 那么拷贝的是指针指向对象的地址(就是一个数值, 至于这个数值有多大, 具体要看运行的平台)也就是指针的值
  • 如果赋值的是一个对象, 那么就会拷贝这个对象

数组也是一种类型: 数组的长度 + 数组保存数据的类型 构成了数组的类型

编译器做了什么才能写 for range 这样的语法呢

源码来自于 go 编译器的 statements.cc, 编译器对 for range 表达式的解析如下

这里编译器对 for range 为 |切片| 类型的处理流程

 // 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 
 //           origin body
 
 // }

对于使用短变量声明的语法块

package main

func main(){
    sl:= []int{1,2,3,4}
    for i,val := range sl{
        _, _ = i , val 
    }
}


在编译器中的实现是如下的:

func main(){
    sl:= []int{1,2,3,4}
    
    // 这里新起一个语法块
    {
        for_temp := sl
        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 
            // origin body
 
         }
        
    } // 新的语句块结束
}

所以, 一开始的那个不知道是不是无止境循环的语句在编译器中的大致实现如下:

package main

import (
    "fmt"
)
func main() {
    sl:= []int{1,2,3,4}
    
    {
        for_temp := sl
        len_temp := len(for_temp)
        for index_temp := 0; index_temp < len_temp; index_temp++ { 
        
            value_temp := for_temp[index_temp] 
            sl = append(sl, value_temp); 
 
         } 
    }
    
    fmt.Println(sl)
}

对于聪明的 gophers 估计已经有了答案了 👍

么么哒? 这就完了么? 其实还没有呢? 文案都准备好了, 就等你们来 👍 呢!