使用赋值语句可以更新一个变量的值,最简单的赋值语句是将变量放在“=”左边,新值放在右边。
x = 1 // 命名变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // 结构体字段赋值
count[x] = count[x] * scale // 数组、slice或map的元素赋值
复合赋值:
count[x] *= scale
数值变量也可以支持++递增和--递减:
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
元组赋值
元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边的对应变量。这对于处理有些同时出现在元组赋值语句左右两边的变量很有帮助,例如,我们可以这样交换两个变量的值:
x, y = y, x
a[i], a[j] = a[j], a[i]
或者是计算两个整数的最大公约数:
func gcd(x, y int) int {
for y != 0 {
x, y = y, x%y
}
return x
}
或者是计算斐波拉契数列:
func fib(n int) int {
x, y := 0, 1
for i := 0; i < n; i++ {
x, y = y, x+y
}
return x
}
元素赋值也可以使一系列琐碎赋值更加紧凑:
i, j, k = 2, 3, 5
但如果表达式太复杂的话,应该尽量避免过度使用元组赋值,因为每个变量单独赋值的写法可读性更好。
有些表达式会产生多个值,比如调用一个有多个返回值的函数。当这样一个函数出现在元组赋值右边的表达式中时,左边变量的数据必须和右边一致。
f, err = os.Open("foo.txt") // function call returns two values
通常,这类函数会用额外的返回值来表达某种错误类型,例如os.Open用额外的返回值返回一个error类型的错误。还有一些会返回布尔值,通常称为ok。在后面会看到三个操作都是类似的用法:map查找、类型断言或通道接收。
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
对于map查找、类型断言或通道接收,并不一定产生两个结构,也可能只产生一个结果。对于返回一个结果的情形,map查找失败时会返回零值,类型断言失败时会发送运行时panic异常、通道接收失败时会返回零值(阻塞不算失败)。例如下面的例子:
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
_, ok = m[key] // map返回2个值
_, ok = mm[""], false // map返回2个值
_ = mm[""] // map返回1个值
和变量声明一样,可以用下划线空白标识符“_”来丢弃不需要的值。
_, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 只检测类型,忽略具体值
可赋值性
赋值语句是显示的赋值形式,但是程序中海油很多其他地方会发生隐式的赋值行为:函数调用会隐式地将实参赋值给形参,一个返回语句会隐式地返回操作的结果,一个复合类型的字面量也会产生赋值行为。例如下面的语句:
medals := []string{"gold", "silver", "bronze"}
隐式地对slice的每个元素进行赋值操作,类似这样写的:
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
map和chan的元素,虽然不是普通的变量,但也有类似的隐式赋值。
无论是隐式还是显式地赋值,在赋值语句的左边和右边都必须有相同的数据类型,只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的。
可赋值性的规则对于不同类型有着不同要求,对每个新类型特殊的地方会专门解释。对于目前我们已经讨论的类型,它的规则是最简答的:类型必须完全匹配,nil可以赋值给任何指针或引用类型的变量。常量则有更灵活的赋值规则,因为这样可以避免不必要的显示的类型转换。
对于两个值是否可以判等,也和可赋值性有关:对于任何类型的值得判等,第二个值 必须是对 第一个值 对应类型的变量是可赋值的。和前面一样,对每个新类型比较特殊的地方做专门的解释。