gopl-zh.github.com/ch2/ch2-04.md
2022-08-04 14:58:52 +08:00

126 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 2.4. 赋值
使用赋值语句可以更新一个变量的值,最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。
```Go
x = 1 // 命名变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // 结构体字段赋值
count[x] = count[x] * scale // 数组、slice或map的元素赋值
```
特定的二元算术运算符和赋值语句的复合操作有一个简洁形式,例如上面最后的语句可以重写为:
```Go
count[x] *= scale
```
这样可以省去对变量表达式的重复计算。
数值变量也可以支持`++`递增和`--`递减语句(译注:自增和自减是语句,而不是表达式,因此`x = i++`之类的表达式是错误的):
```Go
v := 1
v++ // 等价方式 v = v + 1v 变成 2
v-- // 等价方式 v = v - 1v 变成 1
```
### 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) // 只检测类型,忽略具体值
```
### 2.4.2. 可赋值性
赋值语句是显式的赋值形式但是程序中还有很多地方会发生隐式的赋值行为函数调用会隐式地将调用参数的值赋值给函数的参数变量一个返回语句会隐式地将返回操作的值赋值给结果变量一个复合类型的字面量§4.2)也会产生赋值行为。例如下面的语句:
```Go
medals := []string{"gold", "silver", "bronze"}
```
隐式地对slice的每个元素进行赋值操作类似这样写的行为
```Go
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
```
map和chan的元素虽然不是普通的变量但是也有类似的隐式赋值行为。
不管是隐式还是显式地赋值,在赋值语句左边的变量和右边最终的求到的值必须有相同的数据类型。更直白地说,只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的。
可赋值性的规则对于不同类型有着不同要求对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型它的规则是简单的类型必须完全匹配nil可以赋值给任何指针或引用类型的变量。常量§3.6)则有更灵活的赋值规则,因为这样可以避免不必要的显式的类型转换。
对于两个值是否可以用`==`或`!=`进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之亦然。和前面一样,我们会对每个新类型比较特殊的地方做专门的解释。