Golang 筆記 3 if、switch、for、select語句

一、if語句

  Go的流程控制主要包括條件分支、循環和併發。
  if語句一般由if關鍵字、條件表達式和由花括號包裹的代碼塊組成。在Go中,代碼塊必須由花括號包裹。這裏的條件表達式是結果類型爲bool的表達式。例:

if number > 10 {
    number += 3
}

  else分支:

if number > 10 {
    
} else {
    
}

  if語句還支持串聯

if number > 100 {
    
} else if number < 100 {
    
} else {
    
}

  上面的number變量可以用單獨的語句來聲明也可以直接加入到if子句中:

if number := 1; number > 100 {
    
}

  這裏的number := 1被叫做if語句的初始化子句。它應被放置在if關鍵字和條件表達式之間,並與前者由空格間隔,與後者由英文分號間隔。我們在這裏使用了短變量聲明語句,即:在聲明變量number的同時爲它賦值。它的作用域僅在這條if語句所代表的代碼塊中。也可以說,變量number對於該if語句之外的代碼來說是不可見的。
  如果我們在if語句之外聲明瞭number變量,那麼number := 1也是合法的。

var number int
if number := 1; number > 100 {
    
}

  這種寫法有一個專有名詞:標識符的重聲明。只要對同一個標識符的兩次聲明各自所在的代碼塊之間存在包含的關係,就會形成對該標識符的重聲明。具體到這裏,第一次聲明的number變量所在的是該if語句的外層代碼塊,而number := 1所聲明的number變量所在的是該if語句的代碼塊。它們之間存在包含關係,所以對number的重聲明就形成了。
  這種情況造成的結果就是,if語句內部對number的訪問和賦值都只會涉及到第二次聲明的那個number變量。這種現象叫做標識符的遮蔽。

二、switch語句

  switch語句提供了一個多分支條件執行的方法。每一個case可以攜帶一個表達式或一個類型說明符。前者又可被簡稱爲case表達式。因此,Go語言的switch語句又分爲表達式switch語句和類型switch語句。

1、表達式switch語句

var name string
...
switch name {
case "Golang":
    fmt.Println("Golang")
case "Rust":
    fmt.Println("Rust")
default:
    fmt.Println("PHP是世界上最好的語言")
}

  Go會依照從上至下的順序對每一條case語句中case表達式進行求值,只要被發現其表達式與switch表達式的結果相同,該case語句就會被選中。其餘的case語句會被忽略。   與if相同,switch語句還可以包含初始化字句,且其出現位置和寫法如出一轍:

names := []string{"Golang","java","PHP"}
switch name:=names[0];name {
case "Golang":
    fmt.Println("Golang")
...
default:
    fmt.Println("Unknown")
}

2、類型switch語句

  類型switch語句與一般形式有兩點差別。第一點,緊隨case關鍵字的不是表達式,而是類型說明符。類型說明符由若干個類型字面量組成,且多個類型字面量之間由英文逗號分隔。第二點,它的switch表達式是非常特殊的。這種特殊的表達式也起到了類型斷言的作用,但其表現形式很特殊,如:v.(type),其中v必須代表一個接口類型的值。該類表達式只能出現在類型switch語句中,且只能充當switch表達式。一個類型switch語句的示例如下:

v := 11
switch i := interface{}(v).(type) {
case int, int8, int16, int32, int64:
    fmt.Println("A signed integer:%d. The type is %T. \n", v, i)
case uint, uint8, uint16, uint32, uint64:
    fmt.Println("A unsigned integer: %d. The type is %T. \n", v, i)
default:
    fmt.Println("Unknown!")
}

  我們這裏把switch表達式的結果賦給了一個變量。如此以來,我們就可以在該switch語句中使用這個結果了。這段代碼被執行後,輸出:"A signed integer:11. The type is int."
  最後說一下fallthrough。它既是一個關鍵字,又可以代表一條語句。fallthrough語句可被包含在表達式switch語句中的case語句中。它的作用是使控制權流轉到下一個case。不過要注意fallthrough語句僅能作爲case語句中的最後一條語句出現。並且,包含它的case語句不是其所屬switch語句的最後一條case語句。

三、for

  一條for語句通常由關鍵詞for、初始化字句、條件表達式、後置子句和以花括號包裹的代碼塊組成。其中,初始化字句、條件表達式和後置子句之間需用分號分隔。例:

for i := 0; i < 10; i++ {
    fmt.Print(i, " ")
}

  初始化子句、條件表達式、後置子句中的任何一個或多個,不過起到分隔作用的分號一般需要被保留下來。除非在僅有條件表達式或三者全被省略時分號纔可以被一同省略。
  可以把上述的初始化子句、條件表達式、後置子句合稱爲for子句。for語句還有另外一種編寫方式,那就是用range字句替換掉for字句。range子句包含一個或兩個迭代變量(用於與迭代出的值綁定)、特殊標記:=或=、關鍵字range以及range表達式。其中,range表達式的結果值的類型應該是能夠被迭代的,包括:字符串類型、數組類型、數組的指針類型、切片類型、字典類型和通道類型。例:

for i, v := range "Go語言" {
    fmt.Printf("%d: %c\n", i, v)
}

  對於字符串類型的被迭代值來說,for語句每次會迭代出兩個值,第一個值代表第二個值在字符串中的索引,而第二個值則代表該字符串中的某一個字符。迭代是以索引遞增的順序進行的。上面的代碼輸出:

0: G
1: o
2: 5: 

  可以看到迭代出的索引值並不是連續的,字符串的底層是以字節數組的形式存儲的。而在Go中,字符串到字節數組的轉換是通過對其中的每個字符進行UTF-8編碼來完成的。字符串"Go語言"中的每一個字符與相應的字節數組之間的對應關係如下:
  

  一箇中文字符在經過UTF-8編碼之後會表現爲三個字節。所以我們用語[0],語[1]、語[2]分別表示字符'語'經編碼後的第一、二、三個字節。
  這就可以解釋爲什麼會打印出如上內容了:每次迭代出的第一個值所代表的是第二個字符值經編碼後的第一個字節在該字符串經編碼後的字節數組中的索引值。
  對於數組值、數組的指針值和切片來說,range子句每次也會被迭代出兩個值。其中,第一個值會是第二個值在迭代值中的索引,而第二個值則是被迭代值中的某一個元素。同樣的,迭代是以索引遞增的順序進行的。
  對於字典值來說,range子句每次仍然會迭代出兩個值。顯然,第一個值是字典中的某一個鍵,而第二個值則是該鍵對應的那個值。注意,對字典值上的迭代,Go語言是不保證其順序的。
  攜帶range子句的for語句還可以應用在一個通道值之上。其作用是不斷地從該通道值中接受數據,不過每次只會接收一個值。如果通道值中沒有數據,那麼for語句的執行會處於阻塞狀態。無論怎樣,這樣的循環會一直進行下去。直至該通道值被關閉,for語句的執行纔會結束。

四、select語句

  select語句屬於條件分支流程控制語句,不過它只能用於通道。它可以包含若干條case語句,並根據條件選擇其中之一執行。select語句的case關鍵詞只能後跟用於通道的發送操作的表達式以及接受操作的表達式或語句。例:

ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
...
select {
case e1 := <-ch1:
    fmt.Printf("1th case is selected. e1=%v.\n", e1)
case e2 := <-ch2:
    fmt.Printf("2th case is selected. e2=%v.\n", e2)
default:
    fmt.Printf("No data!")
}

  如果該select語句執行的時候通道ch1\ch2都沒有任何數據,那麼肯定只有default case會被執行。但是,只要有一個通道在當時有數據就不會輪到default case執行了。顯然,對於包含通道接收操作的case來講,其執行條件就是通道中存在數據。如果在當時有數據的通道多於一個,那麼Go會通過一種僞隨機的算法來決定哪一個case將被執行。
  另外,對於包含通道發送操作的case來講,其執行條件就是通道中至少還能緩衝一個數據(或者說通道未滿)。類似,當有多個case中的通道未滿時,Go會隨機選擇:

ch3 := make(chan int, 100)
...
select {
case ch3 <- 1:
    fmt.Printf("Sent %d\n", 1)
case ch3 <- 2:
    fmt.Printf("Send %d\n", 2)
default:
    fmt.Println("Full channel!")
}

  該條select語句的兩個case中包含的都是針對通道ch3的發送操作。如果我們把這條語句置於一個循環中,那麼就相當於用有限範圍的隨機整數集合去填滿一個通道。
  **如果一個select語句中不存在default case,並且被執行時其中的所有case都不滿足條件,那麼它的執行就會被阻塞!**當前進程的進行也會因此而停滯。直到其中一個case滿足了執行條件,執行纔會繼續。我們一直在說case 執行條件的滿足與否取決於其操作的通道在當時的狀態。未被初始化的通道會使操作它的case永遠滿足不了執行條件。
  break語句也可以被包含在select語句中的case語句中。它的作用是立即結束當前的select語句的執行。不論其所屬的case語句中是否還有未被執行的語句。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章