go——流程控制
Go在流程控制方面的特點如下:
沒有do和while迴圈,只有一個更廣義的for語句。
switch語句靈活多變,還可以用於型別判斷。
if語句和switch語句都可以包含一條初始化子語句。
break語句和continue語句可以後跟一條標籤(label)語句,以標識需要終止或繼承的程式碼塊。
defer語句可以使我們更加方便地執行異常捕獲和資源回收任務。
select語句也用於多分支選擇,但只與通道配合使用。
go語句用於非同步啟動goroutine並執行指定函式。
1.程式碼塊和作用域
程式碼塊就是一個由花括號包裹地表達式和語句的序列。當然,程式碼塊中也可以不包含任何內容,即:空程式碼塊。
除了顯式地程式碼塊之外,還有一些隱式地程式碼塊,說明如下:
所有Go程式碼形成了一個最大地程式碼塊,即:全域程式碼塊;
每一個程式碼包中的程式碼共同組成了一個程式碼塊,即:程式碼包程式碼塊;
每一個原始碼檔案都是一個程式碼塊,即:原始碼檔案程式碼塊;
每一個if、for、switch和select語句都是一個程式碼塊;
每一個在switch或select語句中的case分支都是一個程式碼塊。
在Go中,使用程式碼塊表示詞法上的作用域範圍,具體規則如下:
一個預定義識別符號的作用域是全域程式碼塊;
表示一個常量、變數、型別或函式(不包括方法),且宣告在函式之外的識別符號的作用域是當前的程式碼包程式碼塊;
被匯入的程式碼包的名稱的作用域是當前的原始碼檔案程式碼塊;
表示方法接收者、方法引數、型別或函式的識別符號,如果被宣告在函式內部,那麼作用域就是包含其宣告的那個最內層的程式碼塊。
此外,我們還可以重新宣告已經在外層程式碼塊中宣告過的識別符號。
當在內層程式碼塊中使用這個識別符號時,它表示的總是那個在該程式碼塊中與它繫結在一起的那個程式實體。
可以說,此時在外層程式碼塊中宣告的那個同名識別符號並遮蔽了。例如
package main import ( "fmt" ) var v = "1,2,3" //最外層識別符號 func main() { v := []int{1, 2, 3} //第二次賦值 if v != nil { var v = 123 //第三次賦值 fmt.Printf("%v\n", v) } } //結果:123
其中,變數v被宣告3次。當判斷v是否非nil時,v代表的時那個切片。
而當v被列印時,它代表的確實那個整數。
2.if語句
if語句會根據條件表示式來執行兩個分支中的一個。
如果那個表示式的結果是true,那麼if分支會被執行,否則else分支會被執行。
例如:
var number int //省略 if 100 < number { number++ }
又如:
if 100 < number { number++ }else { number-- }
if語句還可以包含一條初始化的子語句,用於初始化區域性變數:
if diff := 100 - number;100 < diff { number++ }else { number-- }
此外,它還支援串聯:
if diff := 100 - number;100 < diff { //先進行賦值操作,在邏輯判斷 number++ }else if 200 < diff { number-- }else { number -= 2 }
其中條件表示式的求值順序是自上而下的。只有第一個結果為true的表示式對應的分支會被選中並執行。
並且,只要上面的表示式的結果為true,其後的表示式就不會被求值。
3.switch語句
switch語句也提供了一種多行分支執行的方法。
它會用一個表示式或型別說明符與每一個case進行比較,並決定執行哪一個分支。
(1)表示式switch語句
在表示式switch語句中,switch表示式和所有case攜帶的表示式(也稱為case表示式)都會被求值,並且執行順序是自左向右、自下而上。
只有第一個與switch表示式的求值結果相等case表示式分支會被執行。
如果沒有找到匹配的case表示式並且存在default case,那麼default case的分支會執行。
注意,default case最多隻有一個。
另外,switch表示式可以省略,這時true會作為switch表示式的結果。
示例:
package main import ( "fmt" ) var content string //省略 switch content { default: fmt.Println("不知道什麼語言") case "python": fmt.Fprintln("一門解釋型語言") case "go": fmt.Println("一門編譯型語言")
switch語句也可以包含一條子語句來初始化區域性變數:
switch lang := strings.TrimSpace(content); lang { default: fmt.Println("不知道什麼語言") case "python": fmt.Fprintln("一門解釋型語言") case "go": fmt.Println("一門編譯型語言") }
可以在switch語句中使用fallthrough,來向下一個case語句轉移流程控制權。
switch lang := strings.TrimSpace(content); lang { case "Ruby": fallthrough case "Python": fmt.Println("一門解釋型語言") case "C","Java","Go": fmt.Println("一門編譯型語言") default: fmt.Println("什麼都不是") }
只要lang的值等於Ruby或python,第2個case語句就會執行。其實可以放在一個case中。
每個case語句中的case表示式還可以有多個。
另外,break語句可以用來退出當前的switch語句。它由一個break關鍵字和一個可選的標籤組成。
(2)型別switch語句
型別switch語句將對型別進行判定,而不是值。
var v interface{} //省略 switch v.(type) { case string: fmt.Printf("The string '%s'.\n",v.(string)) case int,uint,int8,uint8: fmt.Printf("The integer is %d.\n",v) default: fmt.Printf("Unsupported value.(type%T)\n",v) }
型別switch語句的switch表示式會包含一個特殊的型別斷言,例如v.(type)。
它雖然特殊,但是也要遵循型別斷言的規則。其次,每個case表示式中包含的都是型別字面量而不是表示式。
最後fallthrough語句不允許出現在型別switch語句中。
型別斷言switch語句的switch表示式還有一種變形寫法。
switch i := v.(type) { case string: fmt.Printf("The string '%s'.\n",i) case int,uint,int8,uint8: fmt.Printf("The integer is %d.\n",i) default: fmt.Printf("Unsupported value.(type%T)\n",i) }
這裡的i := v.(type)使經型別轉換後的值得以儲存。i的型別一定會是v的值的實際型別。
4.for語句
for語句用於根據給定的條件重複執行一個程式碼塊。這個條件或由for子句直接給出,或從range子句中獲得。
(1)for子句
一條for語句中可以攜帶一條for子句。for子句可以包含初始化子句、條件子句和後置子句。
var number int for i := 0; i < 100; i++ { //i := 0初始化語句 i++後置語句 number++ } var j uint = 1 for;j%5 != 0; j *= 3 { //省略初始化子句 number++ } for k := 1; k%5 != 0; { //省略後置子句 k *= 3 number++ }
在for子句的初始化子句和後置子句同時被省略,或者其中的所有部分都省略的情況下,分隔符":"可以省略。
var m = 1 for m < 50 { m *= 3 }
(2)range子句
一條for語句可以攜帶一條range語句,這樣就可以迭代出一個數組或者切片值中的每個元素、
一個字串中的每個字元、或者一個字典值中的每個鍵-元素對,以及持續地接收一個通道型別值中的元素。
隨著迭代的進行,每一次獲取的迭代值(索引、元素、字元或鍵-元素對)都會賦給相應的迭代變數。
ints := []int{1, 2, 3, 4, 5} for i, d := range ints { fmt.Printf("Index:%d,value:%d\n", i, d) }
在range關鍵字右邊的是range表示式。range表示式一般只會在迭代開始前被求值一次.
針對range表示式的不同結果,range子句的行為也會不同。
使用range子句,有3點需要注意:
a.若對陣列、切片或字串值進行迭代,且:=左邊只有一個迭代變數時,一定要小心。
這時只會得到其中元素的索引,而不是元素本身。
b.迭代沒有任何元素的陣列值、為nil的切片值、為nil的字典值或為""的字串值,
並不會執行for語句中的程式碼。for語句在一開始就會直接結束執行,因為這些值的長度都為0.
c.迭代為nil的通道值會讓當前流程永遠阻塞在for語句上。