2.4. 赋值
使用赋值语句可以更新一个变量的值, 最简单的赋值语句是将要被赋值的变量放在 =
的左边, 新值的表达式放在 =
右边.
x = 1 // 命令变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // 结构体字段赋值
count[x] = count[x] * scale // 数组, 切片 或 字典的 元素赋值
特定的赋值语句和二元算术复合操作有一个简洁形式, 例如上面最后的语句可以重写为:
count[x] *= scale
这样可以省去对变量表达式的重复计算.
数值变量也可以支持 ++
递增和 --
递减语句:
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):
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. 在稍后我们看到的三个操作都是类似的行为. 如果 字典查找(§4.3), 类型断言(§7.10), 或 通道接收(§8.4.2) 出现在赋值语句的右边, 它们都将产生两个结果, 有一个额外的布尔结果表示操作是否成功:
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
和变量的声明一样, 我们可以用下划线空白标识符 _
来丢弃不需要的值.
_, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 只检测类型, 忽略具体值
2.4.2. 可赋值性
赋值语句是显示的赋值形式, 但是程序中还有很多地方会发送隐式的赋值行为: 函数调用将隐式地将调用参数的值赋值给函数的参数变量, 一个返回语句将隐式地将返回操作的值赋值给结果变量, 一个复合类型的字面量(§4.2)也会产生赋值行为. 例如下面的语句:
medals := []string{"gold", "silver", "bronze"}
隐式地对切片的每个元素进行赋值操作, 类似这样写的行为:
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
字典和管道的元素, 虽然不是普通的变量, 但是也有类似的隐式赋值行为.
不管是隐式还是显示地赋值, 在赋值语句坐标的变量和右边最终的求到的值必须有相同的数据类型. 更直白地说, 只有右边的值对于左边的变量是可赋值的, 赋值语句才是允许的.
可赋值性的规则对于不同类型有不同要求, 对每个新类型有关的地方我们会专门解释. 对于目前我们已经讨论过的类型, 它的规则是简单的: 类型必须完全匹配, nil 可以赋值给任何指针或引用类型的变量. 常量(§3.6)有更灵活的规则, 这样可以避免不必要的显示类型转换.
对于两个值是否可以用 ==
或 !=
进行相等比较的能力也和可赋值能力有关系:
对于任何的比较, 第一个操作必须是可用于第二个操作类型的变量的赋值的, 反之依然.
和前面一样, 我们会对每个新类型比较有关的地方会做专门解释.