2.4. 賦值

使用賦值語句可以更新一個變量的值,最簡單的賦值語句是將要被賦值的變量放在=的左邊,新值的表達式放在=的右邊。

x = 1                       // 命名變量的賦值
*p = true                   // 通過指針間接賦值
person.name = "bob"         // 結構體字段賦值
count[x] = count[x] * scale // 數組、slice或map的元素賦值

特定的二元算術運算符和賦值語句的複合操作有一個簡潔形式,例如上面最後的語句可以重寫為:

count[x] *= scale

這樣可以省去對變量表達式的重複計算。

數值變量也可以支持++遞增和--遞減語句(譯註:自增和自減是語句,而不是表達式,因此x = i++之類的表達式是錯誤的):

v := 1
v++    // 等價方式 v = v + 1;v 變成 2
v--    // 等價方式 v = v - 1;v 變成 1

2.4.1. 元組賦值

元組賦值是另一種形式的賦值語句,它允許同時更新多個變量的值。在賦值之前,賦值語句右邊的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。這對於處理有些同時出現在元組賦值語句左右兩邊的變量很有幫助,例如我們可以這樣交換兩個變量的值:

x, y = y, x

a[i], a[j] = a[j], a[i]

或者是計算兩個整數值的的最大公約數(GCD)(譯註:GCD不是那個敏感字,而是greatest common divisor的縮寫,歐幾里德的GCD是最早的非平凡算法):

func gcd(x, y int) int {
    for y != 0 {
        x, y = y, x%y
    }
    return x
}

或者是計算斐波納契數列(Fibonacci)的第N個數:

func fib(n int) int {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        x, y = y, x+y
    }
    return x
}

元組賦值也可以使一系列瑣碎賦值更加緊湊(譯註: 特別是在for循環的初始化部分),

i, j, k = 2, 3, 5

但如果表達式太複雜的話,應該儘量避免過度使用元組賦值;因為每個變量單獨賦值語句的寫法可讀性會更好。

有些表達式會產生多個值,比如調用一個有多個返回值的函數。當這樣一個函數調用出現在元組賦值右邊的表達式中時(譯註:右邊不能再有其它表達式),左邊變量的數目必須和右邊一致。

f, err = os.Open("foo.txt") // function call returns two values

通常,這類函數會用額外的返回值來表達某種錯誤類型,例如os.Open是用額外的返回值返回一個error類型的錯誤,還有一些是用來返回布爾值,通常被稱為ok。在稍後我們將看到的三個操作都是類似的用法。如果map查找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊,它們都可能會產生兩個結果,有一個額外的布爾結果表示操作是否成功:

v, ok = m[key]             // map lookup
v, ok = x.(T)              // type assertion
v, ok = <-ch               // channel receive

譯註:map查找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊時,並不一定是產生兩個結果,也可能只產生一個結果。對於只產生一個結果的情形,map查找失敗時會返回零值,類型斷言失敗時會發生運行時panic異常,通道接收失敗時會返回零值(阻塞不算是失敗)。例如下面的例子:

v = m[key]                // map查找,失敗時返回零值
v = x.(T)                 // type斷言,失敗時panic異常
v = <-ch                  // 管道接收,失敗時返回零值(阻塞不算是失敗)

_, ok = m[key]            // map返回2個值
_, ok = mm[""], false     // map返回1個值
_ = mm[""]                // map返回1個值

和變量聲明一樣,我們可以用下劃線空白標識符_來丟棄不需要的值。

_, err = io.Copy(dst, src) // 丟棄字節數
_, ok = x.(T)              // 只檢測類型,忽略具體值

2.4.2. 可賦值性

賦值語句是顯式的賦值形式,但是程序中還有很多地方會發生隱式的賦值行為:函數調用會隱式地將調用參數的值賦值給函數的參數變量,一個返回語句會隱式地將返回操作的值賦值給結果變量,一個複合類型的字面量(§4.2)也會產生賦值行為。例如下面的語句:

medals := []string{"gold", "silver", "bronze"}

隱式地對slice的每個元素進行賦值操作,類似這樣寫的行為:

medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"

map和chan的元素,雖然不是普通的變量,但是也有類似的隱式賦值行為。

不管是隱式還是顯式地賦值,在賦值語句左邊的變量和右邊最終的求到的值必須有相同的數據類型。更直白地說,只有右邊的值對於左邊的變量是可賦值的,賦值語句才是允許的。

可賦值性的規則對於不同類型有著不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必須完全匹配,nil可以賦值給任何指針或引用類型的變量。常量(§3.6)則有更靈活的賦值規則,因為這樣可以避免不必要的顯式的類型轉換。

對於兩個值是否可以用==!=進行相等比較的能力也和可賦值能力有關係:對於任何類型的值的相等比較,第二個值必須是對第一個值類型對應的變量是可賦值的,反之亦然。和前面一樣,我們會對每個新類型比較特殊的地方做專門的解釋。