「读书笔记」理解 Go 语言表达式的求值顺序

364 阅读3分钟

声明、类型、语句与控制结构

17 理解 Go 语言表达式的求值顺序

包级别变量声明语句中的表达式求值顺序:在一个 Go 包内部,包级别变量声明语句的表达式求值顺序是由初始化依赖(initialization dependencies)规则决定的。规则如下:

  • 在 Go 包中,包级别变量的初始化按照变量声明的先后顺序进行。
  • 如果某个变量a的初始化表达式中直接或间接依赖其他变量b,那么变量a的初始化顺序排在变量b后面。
  • 未初始化的且不含有对应初始化表达式或初始化表达式不依赖任何未初始化变量的变量,我们称之为 "ready for initialization" 变量。
  • 包级别变量的初始化是逐步进行的,每一步就是按照变量声明顺序找到下一个 "ready for initialization" 变量并对其进行初始化的过程。反复重复这一步骤,直到没有 "ready for initialization" 变量为止。
  • 位于同一包内但不同文件中的变量的声明顺序依赖编译器处理文件的顺序:先处理的文件中的变量的声明顺序先于后处理的文件中的所有变量。

普通求值顺序:Go 规定表达式操作数中的所有函数、方法以及 channel 操作按照从左到右的次序进行求值。

当普通求值顺序与包级变量的初始化依赖顺序一并使用时,后者优先级更高,但单独每个表达式中的操作数求值依旧按照普通求值顺序的规则。

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int {
   fmt.Println("calling f")
   return c
}

func g() int {

   fmt.Println("calling g")
   return 1
}

func sqr(x int) int {
   fmt.Println("calling sqr")
   return x * x
}

func v() int {
   fmt.Println("calling v")
   return 1
}

func u() int {
   fmt.Println("calling u")
   return 2
}

func main() {
   // 1. 普通求值顺序 + 包级变量求值顺序
   fmt.Println(a, b, c)
   // calling g
   // calling u
   // calling sqr
   // calling v
   // calling f
   // calling v
   // 6 1 5
}

赋值语句的求值:Go 规定赋值语句求值分两个阶段:

  • 第一阶段,对于等号左边的下标表达式、指针解引用表达式和等号右边表达式中的操作数,按照普通求值规则从左到右进行求值。
  • 第二阶段,按从左到右的顺序对变量进行赋值。
func main() {
   // 2. 赋值语句的求值
   n0, n1 := 1, 2
   n0, n1 = n0+n1, n0
   fmt.Println(n0, n1)
   // 3 1
}

switch/select 语句中的表达式求值

  • switch-case 语句中的表达式求值,属于惰性求值,就是需要进行求值时才会对表达值进行求值。

    func Expr(n int) int {
       fmt.Println(n)
       return n
    }
    
    func main() {
       // 3. switch 语句中的表达式求值
       switch Expr(2) {
       case Expr(1), Expr(2), Expr(3):
          fmt.Println("enter into case1")
          fallthrough
       case Expr(4):
          fmt.Println("enter into case2")
       }
       // 2
       // 1
       // 2
       // enter into case1
       // enter into case2
    }
    
  • select-case 语句为我们提供了一种在多个 channel 间实现“多路复用”的机制。select 执行开始时,首先所有 case 表达式都会被按出现的先后顺序求值一遍;如果选择要执行的是一个从 channel 接收数据的 case,那么该 case 等号左边的表达式在接收前才会被求值。

    func getAReadOnlyChannel() <-chan int {
       fmt.Println("invoke getAReadOnlyChannel")
       c := make(chan int)
    
       go func() {
          time.Sleep(3 * time.Second)
          c <- 1
       }()
    
       return c
    }
    
    func getASlice() *[5]int {
       fmt.Println("invoke getASlice")
       var a [5]int
       return &a
    }
    
    func getAWriteOnlyChannel() chan<- int {
       fmt.Println("invoke getAWriteOnlyChannel")
       return make(chan int)
    }
    
    func getANumToChannel() int {
       fmt.Println("invoke getANumToChannel")
       return 2
    }
    
    func main() {
       // 4. select 语句中的表达式求值
       select {
       // recv from channel
       case (getASlice())[0] = <-getAReadOnlyChannel():
          fmt.Println("recv something from a readonly channel")
       // send to channel
       case getAWriteOnlyChannel() <- getANumToChannel():
          fmt.Println("send something to a writeonly channel")
       }
       // invoke getAReadOnlyChannel
       // invoke getAWriteOnlyChannel
       // invoke getANumToChannel
       // invoke getASlice
       // recv something from a readonly channel
    }
    

往期回顾

关注我

掘金:XQGang

Github: XQ-Gang

参考

《Go 语言精进之路:从新手到高手的编程思想、方法和技巧》——白明