1. 程式人生 > >go——流程控制

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語句上。