if語句、for語句和switch語句都屬於Go語言的基本流程控制語句。
下面主要討論for語句和switch語句,不過不講語法規則,而是一些要注意的細節,就是“坑”。
帶range子句的for語句
下面會分別講解迭代變量和range表達式的問題。
迭代變量
對於不同種類的range表達式結果值,for語句的迭代變量的數量可以不同,下面是一個迭代變量的例子:
func main() {
nums := []string{"A", "B", "C", "D", "E", "F"}
for i := range nums {
fmt.Println(i)
}
}
當只有一個迭代變量的時候,數組、數組的指針、切片和字符串的元素值都是無處安放的,只能拿到按照從小到大順序給出的一個個索引值。
如果是兩個迭代變量,就可以去到元素值了:
for i, v := range nums {
fmt.Println(i, v)
}
如果只要取元素只,可以用佔位符:
for _, v := range nums {
fmt.Println(v)
}
迭代數組和切片
這裏主要講range表達式的特性,下面是例子:
package main
import "fmt"
func main() {
nums := [...]int{1, 2, 3, 4, 5, 6}
for i, e := range nums {
if i == len(nums) -1 {
nums[0] += e
fmt.Println(e, nums[0])
} else {
nums[i+1] += e
fmt.Println(e, nums[i+1])
}
}
fmt.Println(nums)
}
/* 執行結果
[7 3 5 7 9 11]
*/
首先要注意,這裏要迭代的是一個數組(不是切片了)。
上面的代碼是在每次迭代的時候,都會去修改下一個元素的值,把下一個元素的值加上當前位置的元素值。問題是在下次迭代的時候,取到的值不是之前上次迭代後修改的值,而是數組原始的那個值。結論概括如下:
- range表達式只會在for語句開始執行時被求值一次,無論後邊會有多少次迭代
- range表達式的求值結果會被複制,就是說,被迭代的對象是range表達式結果值的副本而不是原值
我的理解是,for i, e := range nums
這句,拿去給range表達式處理的是nums的副本。
接下來把上面操作的數組改成切片,就是改成這樣:nums := []int{1, 2, 3, 4, 5, 6}
。之前的數組是值類型,而現在切片是引用類型。再次運行後的結果就不一樣了。結果應該是:[22 3 6 10 15 21]
。因爲是引用類型,range表達式傳遞還是副本,不過這次是地址的副本,所以每次迭代對下一個元素的修改會在之後的迭代中取出上次修改後的值。
switch語句
在switch語句中,有switch表達式和case表達式。
判等操作
下面這段代碼會有編譯錯誤:
package main
import "fmt"
func main() {
v := [...]int8{1, 2, 3, 4, 5, 6}
switch 4 {
case v[0], v[1]:
fmt.Println("1 or 2")
case v[2], v[3]:
fmt.Println("3 or 4")
case v[4], v[5]:
fmt.Println("5 or 6")
}
}
switch表達式需要做判等操作,switch表達式的結果類型,以及各個case表達式的結果類型是要求一樣的。因爲,在Go語言中,只有類型相同的值之間才被允許進行判等操作。
上面的例子中,數組中元素的值是int8類型,而switch表達式的結果是數字常量,如果switch表達式的結果值是無類型的常量,則會被自動斷轉換成此種常量的默認類型值。這裏的常量是整數4,會轉成int類型。然後兩個類型是不同的,所以無法通過編譯。
交換switch表達式和case表達式的類型
下面這段代碼,和上面的例子差不多,只是交換了switch和case裏的類型:
func main() {
v := [...]int8{1, 2, 3, 4, 5, 6}
switch v[4] {
case 1, 2:
fmt.Println("1 or 2")
case 3, 4:
fmt.Println("3 or 4")
case 5, 6:
fmt.Println("5 or 6")
}
}
這次switch表達式是int8類型,而case表達式是常量。如果case表達式的結果值是無類型的常量,則會被自動的轉換爲switch表達式的結果類型。所以這裏case表達式的結果會轉爲int8類型,可以進行判斷操作。
小結:
如果switch表達式的結果值是無類型的常量,那這個常量會被自動的轉換爲此種常量的默認類型的值。
如果case表達式的結果值是無類型的常量,那麼這個常量會被自動的轉換爲switch表達式的結果類型。
最後還要注意,如果表達式的結果類型是接口類型,那麼要小心檢查他們的動態值是否允許判等操作。因爲這個錯誤不會被編譯器發現,也就是不會造成編譯錯誤。但是這樣的後果更加嚴重,會在運行時引發Panic。
case表達式的約束
switch語句在case子句的選擇上是具有唯一性的,也就是switch語句不允許case表達式中的子表達式結果值存在相等的情況,不了這些值是否是在不同的case表達式中。下面的代碼就是因爲有重複的值存在產生了編譯錯誤:
func main() {
v := [...]int{1, 2, 3, 4, 5, 6}
switch v[4] {
case 1, 2 ,3:
fmt.Println("1 or 2")
case 3, 4, 7-1:
fmt.Println("3 or 4")
case 5, 6:
fmt.Println("5 or 6")
}
}
繞過約束
不過上面的這種約束只能在常量裏發現。也就是說有辦法可以繞過這個限制:
package main
import "fmt"
func main() {
v := [...]int{1, 2, 3, 4, 5, 6}
switch v[4] {
case v[0], v[1], v[2]:
fmt.Println("1 or 2")
case v[2], v[3], v[4]:
fmt.Println("3 or 4")
case v[3], v[4], v[5]:
fmt.Println("5 or 6")
}
}
上面用case表達式的常量換成了一個索引表達式,就繞過了編譯的檢查,並且程序也能正確的執行並返回。
類型判斷的switch語句
上面這種繞過約束的方式,對這種switch語句是無效的。因爲,類型switch語句中的case表達式的子表達式,都必須直接由類型字面量表示,而無法通過間接的方式表示。下面是編程會出錯的例子:
func main() {
v := interface{}(byte(127))
switch t := v.(type) {
case uint8, uint16:
fmt.Println("uint8 or uint16")
case byte:
fmt.Println("byte")
default:
fmt.Printf("%T\n", t)
}
}
這裏要知道byte類型是uint8類型的別名類型。這兩個本質上是同一個類型,只是名稱不同。這種情況是無法通過編譯的,因爲子表達式byte和uint8算是重複了。
類型判斷
switch還可以用來做類型判斷,這裏直接給出例子:
package main
import "fmt"
func classifier(items ...interface{}) {
for i, v := range items {
switch v.(type) {
case bool:
fmt.Println("bool", i)
case float64:
fmt.Println("float64", i)
case int:
fmt.Println("int", i)
case nil:
fmt.Println("nil", i)
case string:
fmt.Println("string", i)
default:
fmt.Println("unknow", i)
}
}
}
func main() {
classifier(1, "", nil, 1.234, true, int32(5))
}