1.包级别变量声明语句表达式求值顺序:
在一个Go包内部.包级别变量声明语句的表达式求值顺序是由初始化依赖规则决定的.如下.
1).在Go包中.包级别变量的初始化按照变量声明的先后顺序进行.
2).如果某个变量(如变量a)的初始化表达式中直接或间接依赖其他变量(如变量b).那么变量a的初始化顺序排在变量b后面.
3).未初始化的且不含有对应初始化表达式或初始化表达式不依赖任何未初始化变量的变量.称之为ready for initialization变量.
4).包级别变量的初始化是逐步进行的.每一步就是按照变量声明顺序找到下一个"ready for initialization"变量并对其进行初始化的过程.反复重复这一步骤.直到没有ready for inititlization变量为止.
5).位于同一包内但不同文件中的变量的声明顺序依赖编译器处理文件顺序.先处理的文件中的变量声明顺序先于后处理的文件中的所有变量.
示例:
var (
a = c + b
b = f()
c = f()
d = 3
)
func f() int {
d++
return d
}
func main() {
fmt.Println(a, b, c, d)
}
1).根据规则.包集变量初始化按照变量声明先后顺序进行.因此每一轮寻找"ready for initialization"变量过程都会按照a->b->c->d的顺序依次进行.
2).先来进行第一轮选择"ready for initialization"变量过程.从变量a开始.变量a的初始化表达式为c+b.这使得a的初始化依赖b+c.而b+c通过函数f间接依赖未初始化变量d.因此a并不是"ready for initialization"变量.
3).按声明顺序.接下来是b.b的初始化表达式依赖函数f.而函数f依赖未初始化未初始化变量d.因此b也不是"ready for initialization".
4).按照声明顺序.接下来是c.c的初始化表达式依赖函数f.而函数f依赖未初始化变量d.因此c也不是"ready for initialization".
5).接下来是d.d没有需求值的初始化表达式.而是直接赋予了初值.因此d就是第一轮寻找的"ready for initialization".对其进行初始化d=3.
6).接下来第二轮寻找.依然从a开始.和第一轮一样.b c依旧是未初始化变量.a不满足条件.b依赖函数f.函数f依赖d.但d已经是初始化变量集合中的元素了.b具备了成为"ready for initialization"的条件.于是第二轮对b进行了初始化.b=3+1=4.
7).第三轮寻找.和前两轮一样.c依旧是未初始化变量.a不符合条件.c依赖函数f.函数f依赖d.d已经是已初始化变量集合中的元素了.c具备了条件.对c进行初始化c=4+1=5.
8).进行最后一轮寻找.b和c都已经初始化完成了.所以a=5+4=9.
9).初始化结束.根据上述分析.程序应该输出 9 4 5 5.
执行结果:
如果在包级变量中使用了变量_.空变量也会得到Go编译器一视同仁的对待.
示例:
var (
a = c + b
b = f()
_ = f()
c = f()
d = 3
)
func f() int {
d++
return d
}
func main() {
fmt.Println(a, b, c, d)
}
1).初始化过程按照a->b->_->c->d的顺序进行"ready for initialization"变量查找.
2).第一轮:变量a b _ c都不符合条件.d被选出初始化.d=3.
3).第二轮:变量b符合条件被筛选出并初始化.d=4 b=4
4).第三轮:空变量符合条件被筛选出初始化.空变量忽略了初始值.这一过程的副作用是使得变量d增加1.d=5,b=4.
5).第四轮:变量c符合条件被筛选出初始化.d=6,b=4,c=6.
6).第五轮:变量a符合条件被选出并初始化.d=6,b=4,c=6,a=10
7).包变量初始化结束.分析输出结果.10 4 6 6.
执行结果:
多个变量在声明语句左侧且右侧为单一表达式时的求值情况.这种情况下.无论左侧哪个变量被初始化.同一行的其他变量也会被一并初始化.
示例:
var (
a = c
b, c = f()
d = 3
)
func f() (int, int) {
return d, d + 1
}
func main() {
fmt.Println(a, b, c, d)
}
1).根据包级变量的初始化规则.初始化过程a->b&c->d顺序进行"ready for initialization"变量查找.
2).第一轮:变量 a b c都不符合条件.d被选出初始化.d=3.
3).第二轮.变量b和c一起符合条件.以b被选出为例.b的初始化同时.c也得到初始化.b=3,c=4,d=4.
4).第三轮:变量a符合条件被选出并初始化.d=4,b=3,c=4,a=3.
5).包变量初始化结束.分析输出结果为4 3 4 3.
执行结果:
2.普通求值顺序:
除了包级变量由初始化依赖决定求值顺序.Go语言还定义了普通求值顺序.用于规定表达式操作数中的函数 方法 以及channel的操作求值顺序.Go规定表达式操作数中的所有函数 方法以及channel操作按照从左到右的次序依次进行求值.
示例:
func f() int {
fmt.Println("calling f")
return 1
}
func g(a, b, c int) int {
fmt.Println("calling g")
return 2
}
func h() int {
fmt.Println("calling h")
return 3
}
func i() int {
fmt.Println("calling i")
return 1
}
func j() int {
fmt.Println("calling j")
return 1
}
func k() bool {
fmt.Println("calling k")
return true
}
func main() {
var y = []int{11, 12, 13}
var x = []int{21, 22, 23}
var c chan int = make(chan int)
go func() {
c <- 1
}()
y[f()], _ = g(h(), i()+x[j()], <-c), k()
}
1).按照从左到右的顺序.先对等号左侧表达式操作数中的函数进行调用值.因此第一个是y[f()]中的f().
2).接下来是等号右侧的表达式.第一个函数是g().但g()依赖其参数的求值.其参数列表依然可以看成是一个多赋值操作.其涉及的函数调用顺序从左到右依次为h() i() j() <-c.这样该表达式的求值顺序即为h()->i()->j()->c取值操作->g().
3).最后还剩下末尾的k(),因此该语句中函数以及channel操作的完整求值顺序是.f()->h()->i()->j()->c取值操作->g()->k().
执行结果:
当普通求值顺序与包级变量的初始化顺序一并使用时.后者优先级更高.但每个单独表达式中的操作数求值依旧按照普通求值顺序的规则.
示例:
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() {
fmt.Println(a, b, c)
}
根据包变量初始化依赖规则以及普通求值顺序规则对这个例子进行简要分析.把单行的声明语句等价转换为下面的代码.这样看起来更直观.(注意:与前面多个变量在声明语句左侧且右侧为单一表达式时的表达式求值情况不同.这里右侧并非单一表达式).
var (
a = f() + v()
b = g()
c = sqr(u()) + v()
)
1).根据包变量初始化规则.初始化过程按照"a->b->c"顺序进行"ready for initialization"变量查找.
2).第一轮:变量a依赖c.b符合条件.b被初始化依赖普通求值顺序规则.g被调用.
3).第二轮:变量c符合条件.c被选出初始化.依据普通求值顺序规则,u sqr v先后被调用.
4).第三轮:变量a符合条件.a被选出初始化.依据普通求值顺序规则.f v先后被调用.
5).综合以上分析.得出调用顺序:g->u->sqr->v->f->v.
执行结果:
3.赋值语句求值:
n0 , n1 = n0 + n1 , n0
//或者
n0 , n1 = op( n0 , n1), n0
这是一个赋值语句.Go语言规定.赋值语句求值分为两个阶段.
1).第一阶段.对于等号左边的下标表达式 指针解引用表达式和符号右边表达式中的操作数.按照普通求值规则从左到右求值.
2).第二阶段.按从左到右的顺序对变量进行赋值.
根据上述规则.对这个问题等号两端的表达式的操作数采用从左到右的求值顺序.
假定n0和n1的初始值如下.
n0 , n1 = 1 , 2
1).第一阶段:等号两端表达式求值.上述问题中.等号左边没有需要求值的下标表达式 指针解引用表达式等.只有右端有
n0 + n1 和 n0两个表达式.但表达式的操作数(n0 , n1)都是已初始化的.因此直接将值代入.得到求值结果.求值后.语句可以看成n0,n1=3,1.
2).第二阶段.从左到右赋值.即n0=3,n1=1.
示例:
func example() {
n0, n1 := 1, 2
n0, n1 = n0+n1, n0
fmt.Println(n0, n1)
}
func main() {
example()
}
执行结果:
4.switch/select语句中的表达式求值:
先看switch-case语句中的表达式求值.这类求值属于"惰性求值"范畴.惰性求值指的就是进行求值时才会对表达式进行求值.这样做的目的是让计算机少做事.从而降低对程序的消耗.对性能提升有一定的帮助.
示例:
func expr(n int) int {
fmt.Println(n)
return n
}
func main() {
switch expr(2) {
case expr(1), expr(2), expr(3):
fmt.Println("enter into case1")
fallthrough
case expr(4):
fmt.Println("enter into case2")
}
}
1).对于switch-case语句而言.首先进行求值的是switch后面的表达式expr(2).这个表达式求值时输出2.
2).接下来将按照从上到下 从左到右的顺序对case语句中的表达式进行求值.如果某个表达式的结果与switch表达式结果一致.那么求值停止.后面未求值的case表达式将被忽略.
3).fallthrough将执行权直接转移到下一个case执行语句中.略过了case表达式expr(4)的求值.
执行结果:
Go语言中的select语句为我们提供了一种在多个channel间实现多路复用的机制.是编写Go并发程序最常见的语句之一.
示例:
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 getAWriteChannel() chan<- int {
fmt.Println("invoke getAWriteChannel")
return make(chan int)
}
func getANumToChannel() int {
fmt.Println("invoke getANumToChannel")
return 2
}
func main() {
select {
//从channel接收数据
case (getASlice())[0] = <-getAReadOnlyChannel():
fmt.Println("recv something from a readonly channel")
case getAWriteChannel() <- getANumToChannel():
fmt.Println("send something to a writeonly channel")
}
}
1).select在执行的时候.首先所有case表达式都会按出现的先后顺序求值一遍.
有一个例外.位于case等号左边的从channel接收数据的表达式(RecvStmt)不会被求值.这里对应的是getASilce.
2).如果选择要执行的是一个从channel接收数据的case.那么该case等号左边的表达式在接收前才会被求值.上面的例子中.在getAReadOnlyChannel创建的goroutine在3s后向channel中写入一个int值后.select选择了第一个case执行.此时对等号左侧的表达式(getASlice())[0]进行求值.输出"invoke getASlice".也算是一种惰性求值.
执行结果:
阑珊火树鱼龙舞,望中宝钗楼远。鞣鞠余红,琉璃剩碧,待属花归缓缓。寒轻漏浅。正乍敛烟霏,陨星如箭。旧事惊心,一双莲影藕丝断。
莫恨流年似水,恨消残蝶粉,韶光忒浅。细语吹香,暗尘笼撰,都逐晓风零乱。阑干敲遍。问帘底纤纤,甚时重见?不解相思,月华今夜满。 纳兰
语雀地址www.yuque.com/itbosunmian…?
《Go.》 密码:xbkk 欢迎大家访问.提意见.
如果大家喜欢我的分享的话.可以关注我的微信公众号
念何架构之路