### 2.4.1. 元组赋值 元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有帮助,例如我们可以这样交换两个变量的值: ```Go x, y = y, x a[i], a[j] = a[j], a[i] ``` 或者是计算两个整数值的的最大公约数(GCD)(译注:GCD不是那个敏感字,而是greatest common divisor的缩写,欧几里德的GCD是最早的非平凡算法): ```Go func gcd(x, y int) int { for y != 0 { x, y = y, x%y } return x } ``` 或者是计算斐波纳契数列(Fibonacci)的第N个数: ```Go func fib(n int) int { x, y := 0, 1 for i := 0; i < n; i++ { x, y = y, x+y } return x } ``` 元组赋值也可以使一系列琐碎赋值更加紧凑(译注: 特别是在for循环的初始化部分), ```Go i, j, k = 2, 3, 5 ``` 但如果表达式太复杂的话,应该尽量避免过度使用元组赋值;因为每个变量单独赋值语句的写法可读性会更好。 有些表达式会产生多个值,比如调用一个有多个返回值的函数。当这样一个函数调用出现在元组赋值右边的表达式中时(译注:右边不能再有其它表达式),左边变量的数目必须和右边一致。 ```Go f, err = os.Open("foo.txt") // function call returns two values ``` 通常,这类函数会用额外的返回值来表达某种错误类型,例如os.Open是用额外的返回值返回一个error类型的错误,还有一些是用来返回布尔值,通常被称为ok。在稍后我们将看到的三个操作都是类似的用法。如果map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边,它们都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功: ```Go 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异常,通道接收失败时会返回零值(阻塞不算是失败)。例如下面的例子: ```Go v = m[key] // map查找,失败时返回零值 v = x.(T) // type断言,失败时panic异常 v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败) _, ok = m[key] // map返回2个值 _, ok = mm[""], false // map返回1个值 _ = mm[""] // map返回1个值 ``` 和变量声明一样,我们可以用下划线空白标识符`_`来丢弃不需要的值。 ```Go _, err = io.Copy(dst, src) // 丢弃字节数 _, ok = x.(T) // 只检测类型,忽略具体值 ```