Go 运算符详解
运算符就是程序里的加减乘除、比较、逻辑判断这些符号。Go 的运算符和 C/Java 非常像,熟悉的话几乎可以无缝上手,但 Go 也有几个自己的小特色,比如位清除 &^、以及自增自减只能当语句用。本篇按类别逐个讲清楚,并在末尾给出运算符优先级。
一、算术运算符
最基础的加减乘除,加上取余和自增自减。需要特别留意的是整数除法会截断小数——两个整数相除,结果还是整数,小数部分直接丢掉,不会四舍五入。想要小数,至少要有一个操作数是浮点数。
package main
import "fmt"
func main() {
a, b := 17, 5
fmt.Println(a+b, a-b, a*b) // 22 12 85
fmt.Println(a / b) // 3(整数除法,直接截断小数)
fmt.Println(a % b) // 2(取余)
fmt.Println(7.0 / 2.0) // 3.5(浮点数才保留小数)
n := 1
n++ // 自增,等价于 n = n + 1
n-- // 自减
fmt.Println(n) // 1
}还有一点和 C/Java 不同:Go 的 ++、-- 只能作为语句,不能作为表达式。也就是说 b = a++ 这种写法在 Go 里是非法的,你只能单独写一行 a++。Go 也不区分前置和后置(++a 是错的),这避免了"自增写在前面还是后面"造成的混乱。
二、关系运算符
用来比较两个值,结果一定是布尔型 bool。一共六个:
==相等、!=不等>大于、<小于>=大于等于、<=小于等于
package main
import "fmt"
func main() {
a, b := 3, 5
fmt.Println(a == b) // false
fmt.Println(a != b) // true
fmt.Println(a > b) // false
fmt.Println(a < b) // true
fmt.Println(a >= b) // false
fmt.Println(a <= b) // true
}注意:Go 强类型,不同类型的值不能直接比较(比如不能拿 int 和 float64 用 ==),需要先显式转换。
三、逻辑运算符
用于组合多个布尔条件,结果还是布尔值:
&&逻辑与(两个都真才真)||逻辑或(有一个真就真)!逻辑非(真假翻转)
&& 和 || 都有短路特性:左边能确定结果时就不再计算右边。比如 a != 0 && b/a > 1,当 a 为 0 时右边根本不会执行,从而避免了除以零的崩溃。利用短路可以写出既简洁又安全的判断。
package main
import "fmt"
func main() {
age := 20
hasID := true
// && 两个都为真,结果才为真
fmt.Println(age >= 18 && hasID) // true
// || 只要有一个为真,结果就为真
fmt.Println(age < 18 || !hasID) // false
}四、位运算符
位运算直接操作二进制位,常用于底层、网络、加密、权限标志等场景。Go 支持的位运算符:
&按位与、|按位或、^按位异或<<左移、>>右移(空出来的位补 0)&^位清除(bit clear):Go 特有的运算符,等价于a & ^b,作用是把a中b为 1 的那些位清掉。
package main
import "fmt"
func main() {
a, b := 0b1100, 0b1010 // 12 和 10(二进制字面量,Go 1.13+)
fmt.Printf("a & b = %b\n", a&b) // 1000 按位与
fmt.Printf("a | b = %b\n", a|b) // 1110 按位或
fmt.Printf("a ^ b = %b\n", a^b) // 0110 按位异或
fmt.Printf("a &^ b = %b\n", a&^b) // 0100 位清除(Go 特有)
fmt.Printf("a << 2 = %b\n", a<<2) // 110000 左移 2 位
fmt.Printf("a >> 1 = %b\n", a>>1) // 110 右移 1 位
}五、赋值运算符
除了最基本的 =,Go 还提供了一堆"复合赋值"运算符,把运算和赋值合成一步,写起来更简洁:
- 算术类:
+=-=*=/=%= - 位移类:
<<=>>= - 位运算类:
&=|=^=&^=
package main
import "fmt"
func main() {
var a int = 20
a += 5 // a = a + 5
a -= 3
a *= 2
a /= 4
a %= 4
a <<= 2
a &= 6
a ^= 2
a |= 1
fmt.Println(a)
}a += 5 就是 a = a + 5 的简写,其余同理。复合赋值让代码更紧凑,也少写一次变量名。
六、其他运算符
还有几个常用但不属于上面分类的运算符,先认识一下:
&取地址(得到指针)、*解引用(读取指针指向的值)<-channel 的发送和接收(并发编程的核心,后续详解)
package main
import "fmt"
func main() {
x := 42
p := &x // & 取地址,p 是指向 x 的指针
fmt.Println(*p) // * 解引用,读取指针指向的值:42
*p = 100 // 通过指针修改 x
fmt.Println(x) // 100
// <- 是 channel 的收发运算符,留到并发篇详解
// ch <- 1 // 向 channel 发送数据
// v := <-ch // 从 channel 接收数据
}七、运算符优先级
从高到低排列(同一行内同级,按从左到右结合):
*/%<<>>&&^+-|^==!=<<=>>=&&||
一元运算符(!、负号 -、取地址 &、解引用 *、接收 <-)优先级最高。优先级记不住完全没关系,合理地用括号让意图清晰,比死记优先级更可靠,代码也更易读。
小结
算术、关系、逻辑、位、赋值五大类是日常主力;整数除法截断小数、++ 只能当语句用是两个 Go 小规矩;位清除 &^ 是 Go 的小特色;指针和 channel 运算符先混个脸熟,后面会专门讲。下一篇进入条件语句。