[TOC]
Go的控制結構與C的相關,但是有重要的區別。沒有`do`或者`while`循環,只有一個稍微廣義的`for`;`switch`更加靈活;`if`和`switch`接受一個像`for`那樣可選的初始化語句;`break`和`continue`語句接受一個可選的標號來指定中斷或繼續什么;還有一些新的控制結構,包括類型switch和多路通信復用器(multiway communications multiplexer),`select`。語句也稍微有些不同:沒有圓括號,并且控制結構體必須總是由大括號包裹。
## If
Go中,簡單的`if`看起來是這樣的:
~~~
if x > 0 {
return y
}
~~~
強制的大括號可以鼓勵大家在多行中編寫簡單的`if`語句。不管怎樣,這是一個好的風格,特別是當控制結構體包含了一條控制語句,例如`return`或者`break`。
既然`if`和`switch`接受一個初始化語句,那么常見的方式是用來建立一個局部變量。
~~~
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
~~~
在Go的庫中,你會發現當`if`語句不會流向下一條語句時—也就是說,控制結構體結束于`break`,`continue`,`goto`或者`return`—則不必要的`else`會被省略掉。
~~~
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
~~~
這個例子是一種常見的情況,代碼必須防范一系列的錯誤條件。如果成功的控制流是沿著頁面往下走,來消除它們引起的錯誤情況,那么代碼會非常易讀。由于錯誤情況往往會結束于`return`語句,因此代碼不需要有`else`語句。
~~~
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
~~~
## 重新聲明和重新賦值
另外:上一章節的最后一個例子,展示了`:=`短聲明形式的工作細節。該聲明調用了`os.Open`進行讀取,
~~~
f, err := os.Open(name)
~~~
該語句聲明了兩個變量,`f`和`err`。幾行之后,又調用了`f.Stat`進行讀取,
~~~
d, err := f.Stat()
~~~
這看起來像是又聲明了`d`和`err`。但是,注意`err`在兩條語句中都出現了。這種重復是合法的:`err`是在第一條語句中被聲明,而在第二條語句中只是被*重新賦值*。這意味著使用之前已經聲明過的`err`變量調用`f.Stat`,只會是賦給其一個新的值。
在`:=`聲明中,變量`v`即使已經被聲明過,也可以出現,前提是:
* 該聲明和`v`已有的聲明在相同的作用域中(如果`v`已經在外面的作用域里被聲明了,則該聲明將會創建一個新的變量 §)
* 初始化中相應的值是可以被賦給`v`的
* 并且,聲明中至少有其它一個變量將被聲明為一個新的變量
這種不尋常的屬性純粹是從實用主義方面來考慮的。例如,這會使得在一個長的`if-else`鏈中,很容易地使用單個`err`值。你會經常看到這種用法。
§ 值得一提的是,在Go中,函數參數和返回值的作用域與函數體的作用域是相同的,雖然它們在詞法上是出現在包裹函數體的大括號外面。
## For
Go的`for`循環類似于—但又不等同于—C的。它統一了`for`和`while`,并且沒有`do-while`。有三種形式,其中只有一個具有分號。
~~~
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
~~~
短聲明使得在循環中很容易正確的聲明索引變量。
~~~
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
~~~
如果你是在數組,切片,字符串或者map上進行循環,或者從channel中進行讀取,則可以使用`range`子句來管理循環。
~~~
for key, value := range oldMap {
newMap[key] = value
}
~~~
如果你只需要range中的第一項(key或者index),則可以丟棄第二個:
~~~
for key := range m {
if key.expired() {
delete(m, key)
}
}
~~~
如果你只需要range中的第二項(value),則可以使用*空白標識符*,一個下劃線,來丟棄第一個:
~~~
sum := 0
for _, value := range array {
sum += value
}
~~~
空白標識符有許多用途,這在[后面的章節](http://www.hellogcc.org/effective_go.html#blank)中會有介紹。
對于字符串,`range`會做更多的事情,通過解析UTF-8來拆分出單個的Unicode編碼點。錯誤的編碼會消耗一個字節,產生一個替代的符文(rune)U+FFFD。(名字(與內建類型相關聯的)`rune`是Go的術語,用于指定一個單獨的Unicode編碼點。詳情參見[the language specification](http://golang.org/ref/spec#Rune_literals))循環
~~~
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
~~~
會打印出
~~~
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '?' starts at byte position 6
character U+8A9E '語' starts at byte position 7
~~~
最后,Go沒有逗號操作符,并且`++`和`--`是語句而不是表達式。因此,如果你想在`for`中運行多個變量,你需要使用并行賦值(盡管這樣會阻礙使用`++`和`--`)。
~~~
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
~~~
## Switch
Go的`switch`要比C的更加通用。表達式不需要為常量,甚至不需要為整數,case是按照從上到下的順序進行求值,直到找到匹配的。如果`switch`沒有表達式,則對`true`進行匹配。因此,可以—按照語言習慣—將`if`-`else`-`if`-`else`鏈寫成一個`switch`。
~~~
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
~~~
switch不會自動從一個case子句跌落到下一個case子句。但是case可以使用逗號分隔的列表。
~~~
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
~~~
雖然和其它類C的語言一樣,使用`break`語句來提前中止`switch`在Go中幾乎不怎么常見。不過,有時候是需要中斷包含它的循環,而不是switch。在Go中,可以通過在循環上加一個標號,然后“breaking”到那個標號來達到目的。該例子展示了這些用法。
~~~
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
~~~
當然,`continue`語句也可以接受一個可選的標號,但是只能用于循環。
作為這個章節的結束,這里有一個對字節切片進行比較的程序,使用了兩個`switch`語句:
~~~
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
~~~
## 類型switch
switch還可用于獲得一個接口變量的動態類型。這種*類型switch*使用類型斷言的語法,在括號中使用關鍵字`type`。如果switch 在表達式中聲明了一個變量,則變量會在每個子句中具有對應的類型。比較符合語言習慣的方式是在這些case里重用一個名字,實際上是在每個case里聲名一個新的變量,其具有相同的名字,但是不同的類型。
~~~
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
~~~