Go語言表達(dá)式的求值順序

1.包級別變量聲明語句表達(dá)式求值順序:
在一個(gè)Go包內(nèi)部.包級別變量聲明語句的表達(dá)式求值順序是由初始化依賴規(guī)則決定的.如下.
1).在Go包中.包級別變量的初始化按照變量聲明的先后順序進(jìn)行.
2).如果某個(gè)變量(如變量a)的初始化表達(dá)式中直接或間接依賴其他變量(如變量b).那么變量a的初始化順序排在變量b后面.
3).未初始化的且不含有對應(yīng)初始化表達(dá)式或初始化表達(dá)式不依賴任何未初始化變量的變量.稱之為ready for initialization變量.
4).包級別變量的初始化是逐步進(jìn)行的.每一步就是按照變量聲明順序找到下一個(gè)"ready for initialization"變量并對其進(jìn)行初始化的過程.反復(fù)重復(fù)這一步驟.直到?jīng)]有ready for inititlization變量為止.
5).位于同一包內(nèi)但不同文件中的變量的聲明順序依賴編譯器處理文件順序.先處理的文件中的變量聲明順序先于后處理的文件中的所有變量.
示例:
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).根據(jù)規(guī)則.包集變量初始化按照變量聲明先后順序進(jìn)行.因此每一輪尋找"ready for initialization"變量過程都會(huì)按照a->b->c->d的順序依次進(jìn)行.
2).先來進(jìn)行第一輪選擇"ready for initialization"變量過程.從變量a開始.變量a的初始化表達(dá)式為c+b.這使得a的初始化依賴b+c.而b+c通過函數(shù)f間接依賴未初始化變量d.因此a并不是"ready for initialization"變量.
3).按聲明順序.接下來是b.b的初始化表達(dá)式依賴函數(shù)f.而函數(shù)f依賴未初始化未初始化變量d.因此b也不是"ready for initialization".
4).按照聲明順序.接下來是c.c的初始化表達(dá)式依賴函數(shù)f.而函數(shù)f依賴未初始化變量d.因此c也不是"ready for initialization".
5).接下來是d.d沒有需求值的初始化表達(dá)式.而是直接賦予了初值.因此d就是第一輪尋找的"ready for initialization".對其進(jìn)行初始化d=3.
6).接下來第二輪尋找.依然從a開始.和第一輪一樣.b c依舊是未初始化變量.a不滿足條件.b依賴函數(shù)f.函數(shù)f依賴d.但d已經(jīng)是初始化變量集合中的元素了.b具備了成為"ready for initialization"的條件.于是第二輪對b進(jìn)行了初始化.b=3+1=4.
7).第三輪尋找.和前兩輪一樣.c依舊是未初始化變量.a不符合條件.c依賴函數(shù)f.函數(shù)f依賴d.d已經(jīng)是已初始化變量集合中的元素了.c具備了條件.對c進(jìn)行初始化c=4+1=5.
8).進(jìn)行最后一輪尋找.b和c都已經(jīng)初始化完成了.所以a=5+4=9.
9).初始化結(jié)束.根據(jù)上述分析.程序應(yīng)該輸出 9 4 5 5.
執(zhí)行結(jié)果:

如果在包級變量中使用了變量_.空變量也會(huì)得到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的順序進(jìn)行"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).包變量初始化結(jié)束.分析輸出結(jié)果.10 4 6 6.
執(zhí)行結(jié)果:

多個(gè)變量在聲明語句左側(cè)且右側(cè)為單一表達(dá)式時(shí)的求值情況.這種情況下.無論左側(cè)哪個(gè)變量被初始化.同一行的其他變量也會(huì)被一并初始化.
示例:
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).根據(jù)包級變量的初始化規(guī)則.初始化過程a->b&c->d順序進(jìn)行"ready for initialization"變量查找.
2).第一輪:變量 a b c都不符合條件.d被選出初始化.d=3.
3).第二輪.變量b和c一起符合條件.以b被選出為例.b的初始化同時(shí).c也得到初始化.b=3,c=4,d=4.
4).第三輪:變量a符合條件被選出并初始化.d=4,b=3,c=4,a=3.
5).包變量初始化結(jié)束.分析輸出結(jié)果為4 3 4 3.
執(zhí)行結(jié)果:

2.普通求值順序:
除了包級變量由初始化依賴決定求值順序.Go語言還定義了普通求值順序.用于規(guī)定表達(dá)式操作數(shù)中的函數(shù) 方法 以及channel的操作求值順序.Go規(guī)定表達(dá)式操作數(shù)中的所有函數(shù) 方法以及channel操作按照從左到右的次序依次進(jìn)行求值.
示例:
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).按照從左到右的順序.先對等號左側(cè)表達(dá)式操作數(shù)中的函數(shù)進(jìn)行調(diào)用值.因此第一個(gè)是y[f()]中的f().
2).接下來是等號右側(cè)的表達(dá)式.第一個(gè)函數(shù)是g().但g()依賴其參數(shù)的求值.其參數(shù)列表依然可以看成是一個(gè)多賦值操作.其涉及的函數(shù)調(diào)用順序從左到右依次為h() i() j() <-c.這樣該表達(dá)式的求值順序即為h()->i()->j()->c取值操作->g().
3).最后還剩下末尾的k(),因此該語句中函數(shù)以及channel操作的完整求值順序是.f()->h()->i()->j()->c取值操作->g()->k().
執(zhí)行結(jié)果:

當(dāng)普通求值順序與包級變量的初始化順序一并使用時(shí).后者優(yōu)先級更高.但每個(gè)單獨(dú)表達(dá)式中的操作數(shù)求值依舊按照普通求值順序的規(guī)則.
示例:
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)
}根據(jù)包變量初始化依賴規(guī)則以及普通求值順序規(guī)則對這個(gè)例子進(jìn)行簡要分析.把單行的聲明語句等價(jià)轉(zhuǎn)換為下面的代碼.這樣看起來更直觀.(注意:與前面多個(gè)變量在聲明語句左側(cè)且右側(cè)為單一表達(dá)式時(shí)的表達(dá)式求值情況不同.這里右側(cè)并非單一表達(dá)式).
var (
a = f() + v()
b = g()
c = sqr(u()) + v()
)1).根據(jù)包變量初始化規(guī)則.初始化過程按照"a->b->c"順序進(jìn)行"ready for initialization"變量查找.
2).第一輪:變量a依賴c.b符合條件.b被初始化依賴普通求值順序規(guī)則.g被調(diào)用.
3).第二輪:變量c符合條件.c被選出初始化.依據(jù)普通求值順序規(guī)則,u sqr v先后被調(diào)用.
4).第三輪:變量a符合條件.a被選出初始化.依據(jù)普通求值順序規(guī)則.f v先后被調(diào)用.
5).綜合以上分析.得出調(diào)用順序:g->u->sqr->v->f->v.
執(zhí)行結(jié)果:

3.賦值語句求值:
n0 , n1 = n0 + n1 , n0
//或者
n0 , n1 = op( n0 , n1), n0
這是一個(gè)賦值語句.Go語言規(guī)定.賦值語句求值分為兩個(gè)階段.
1).第一階段.對于等號左邊的下標(biāo)表達(dá)式 指針解引用表達(dá)式和符號右邊表達(dá)式中的操作數(shù).按照普通求值規(guī)則從左到右求值.
2).第二階段.按從左到右的順序?qū)ψ兞窟M(jìn)行賦值.
根據(jù)上述規(guī)則.對這個(gè)問題等號兩端的表達(dá)式的操作數(shù)采用從左到右的求值順序.
假定n0和n1的初始值如下.
n0 , n1 = 1 , 2
1).第一階段:等號兩端表達(dá)式求值.上述問題中.等號左邊沒有需要求值的下標(biāo)表達(dá)式 指針解引用表達(dá)式等.只有右端有
n0 + n1 和 n0兩個(gè)表達(dá)式.但表達(dá)式的操作數(shù)(n0 , n1)都是已初始化的.因此直接將值代入.得到求值結(jié)果.求值后.語句可以看成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()
}執(zhí)行結(jié)果:

4.switch/select語句中的表達(dá)式求值:
先看switch-case語句中的表達(dá)式求值.這類求值屬于"惰性求值"范疇.惰性求值指的就是進(jìn)行求值時(shí)才會(huì)對表達(dá)式進(jìn)行求值.這樣做的目的是讓計(jì)算機(jī)少做事.從而降低對程序的消耗.對性能提升有一定的幫助.
示例:
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語句而言.首先進(jìn)行求值的是switch后面的表達(dá)式expr(2).這個(gè)表達(dá)式求值時(shí)輸出2.
2).接下來將按照從上到下 從左到右的順序?qū)ase語句中的表達(dá)式進(jìn)行求值.如果某個(gè)表達(dá)式的結(jié)果與switch表達(dá)式結(jié)果一致.那么求值停止.后面未求值的case表達(dá)式將被忽略.
3).fallthrough將執(zhí)行權(quán)直接轉(zhuǎn)移到下一個(gè)case執(zhí)行語句中.略過了case表達(dá)式expr(4)的求值.
執(zhí)行結(jié)果:

Go語言中的select語句為我們提供了一種在多個(gè)channel間實(shí)現(xiàn)多路復(fù)用的機(jī)制.是編寫Go并發(fā)程序最常見的語句之一.
示例:
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接收數(shù)據(jù)
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在執(zhí)行的時(shí)候.首先所有case表達(dá)式都會(huì)按出現(xiàn)的先后順序求值一遍.
有一個(gè)例外.位于case等號左邊的從channel接收數(shù)據(jù)的表達(dá)式(RecvStmt)不會(huì)被求值.這里對應(yīng)的是getASilce.
2).如果選擇要執(zhí)行的是一個(gè)從channel接收數(shù)據(jù)的case.那么該case等號左邊的表達(dá)式在接收前才會(huì)被求值.上面的例子中.在getAReadOnlyChannel創(chuàng)建的goroutine在3s后向channel中寫入一個(gè)int值后.select選擇了第一個(gè)case執(zhí)行.此時(shí)對等號左側(cè)的表達(dá)式(getASlice())[0]進(jìn)行求值.輸出"invoke getASlice".也算是一種惰性求值.
執(zhí)行結(jié)果:

到此這篇關(guān)于Go語言表達(dá)式的求值順序的文章就介紹到這了,更多相關(guān)Go語言表達(dá)式求值順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Go基礎(chǔ)教程之正則表達(dá)式regexp庫示例詳解
- 正則表達(dá)式詳解以及Golang中的應(yīng)用示例
- Go expr 通用表達(dá)式引擎的使用
- Go語言結(jié)合正則表達(dá)式實(shí)現(xiàn)高效獲取數(shù)據(jù)
- Go正則表達(dá)式匹配字符串,替換字符串方式
- Go語言利用正則表達(dá)式處理多行文本
- Go語言實(shí)戰(zhàn)之詳細(xì)掌握正則表達(dá)式的應(yīng)用與技巧
- 在?Go?語言中使用?regexp?包處理正則表達(dá)式的操作
- Go語句與表達(dá)式案例手冊深度解析
- 一文帶你全面掌握Go語言中的正則表達(dá)式
相關(guān)文章
Golang實(shí)現(xiàn)請求限流的幾種辦法(小結(jié))
這篇文章主要介紹了Golang實(shí)現(xiàn)請求限流的幾種辦法(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
Go開發(fā)Gin項(xiàng)目添加jwt功能實(shí)例詳解
這篇文章主要為大家介紹了Go開發(fā)Gin項(xiàng)目中添加jwt功能實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Go語言基礎(chǔ)學(xué)習(xí)之?dāng)?shù)組的使用詳解
數(shù)組相必大家都很熟悉,各大語言也都有數(shù)組的身影。Go 語言也提供了數(shù)組類型的數(shù)據(jù)結(jié)構(gòu)。本文就來通過一些簡單的示例帶大家了解一下Go語言中數(shù)組的使用,希望對大家有所幫助2022-12-12

