Operator(运算符)
运算符用于组合操作数并产生结果。Go 的运算符集合较小,但操作结果严格依赖操作数类型:整数除法不会产生小数,逻辑条件不能使用“真值”替代布尔值,一些类型也不能使用 == 相互比较。
算术运算符
常用算术运算符包括 +、-、*、/ 和 %:
package main
import "fmt"
func main() {
fmt.Println(7 + 2) // 9
fmt.Println(7 - 2) // 5
fmt.Println(7 * 2) // 14
fmt.Println(7 / 2) // 3
fmt.Println(7 % 2) // 1
fmt.Println(7.0 / 2) // 3.5
fmt.Println("Go" + "lang") // Golang
}
两个整数相除时,商向零截断;要保留小数,至少需要将操作数转换到合适的浮点类型。% 只适用于整数。除数为常量零会导致编译错误;程序运行时遇到整数除以零则会 panic,因此外部输入必须先校验。
一元 + 保持数值不变,一元 - 改变数值符号。二元 + 还可拼接字符串,但频繁拼接大量字符串时应根据场景考虑 strings.Builder 等标准库工具。
比较运算符
比较运算符包括 ==、!=、<、<=、> 和 >=,结果总是 bool:
package main
import "fmt"
func main() {
age := 20
fmt.Println(age >= 18) // true
fmt.Println("Go" < "Rust") // true,按字节序列逐字典序比较
}
能否比较取决于类型:
- 布尔值只能使用
==和!=。 - 整数、浮点数和字符串属于有序类型,可以使用六种比较运算符。
- 复数可以判断相等,但不能比较大小。
- 数组在元素类型可比较时可以比较;结构体在所有字段都可比较时可以比较。
- 切片、映射和函数只能与
nil比较,不能彼此使用==。
接口值相互比较时,如果其动态值类型不可比较,比较会在运行时 panic。接受任意值的通用代码不能假设所有接口值都可以安全使用 ==。
逻辑运算符
&& 表示逻辑与,|| 表示逻辑或,! 表示逻辑非。操作数必须是布尔值:
package main
import "fmt"
func main() {
authenticated := true
age := 20
if authenticated && age >= 18 {
fmt.Println("allowed")
}
}
&& 和 || 使用短路求值。左侧已经能确定结果时,右侧不会执行:
if user != nil && user.Active {
// user 为 nil 时不会读取 user.Active。
}
Go 不会把 0、空字符串、nil 或空集合自动转成 false。if count {} 不是合法写法,必须明确写成 if count != 0 {}。
位运算与移位
整数支持以下位运算:
| 运算符 | 含义 |
|---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或;作为一元运算符时按位取反 |
&^ | 位清除 |
<< | 左移 |
>> | 右移 |
package main
import "fmt"
func main() {
const (
read = 1 << iota
write
)
permissions := read | write
fmt.Println(permissions&read != 0) // true
permissions &^= write
fmt.Println(permissions&write != 0) // false
}
iota 在每个常量组中从 0 开始。如果把 read 和 write 写成两个独立的 const 声明,两者都会得到 1,无法表示不同权限位。
移位次数必须是非负整数。移位和类型转换组合时,应加括号明确意图,并检查目标类型范围,避免截断高位。
赋值、自增与自减
+=、-=、*=、/=、%=、&= 等复合赋值会读取并更新左侧变量:
count := 2
count += 3
++ 和 -- 在 Go 中是语句,不是表达式,而且只有后缀形式:
count++
count--
因此不能写 total = count++、++count,也不能把它们放在表达式中。赋值本身同样不产生可继续参与运算的值。
指针与通道相关运算符
同一个符号在不同上下文中可能有不同含义:
&value取得值的地址。*pointer读取指针指向的值;在类型位置,*T表示指向T的指针类型。channel <- value向通道发送值。<-channel从通道接收值。
package main
import "fmt"
func main() {
count := 1
pointer := &count
*pointer = 2
values := make(chan int, 1)
values <- *pointer
fmt.Println(<-values)
}
通道操作还涉及阻塞、关闭和并发同步语义,应在学习并发专题时结合 goroutine 与 select 进一步理解。
优先级
一元运算符优先级最高。二元运算符从高到低分为五级:
| 优先级 | 运算符 |
|---|---|
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 | || |
相同优先级按从左到右结合。即使记得规则,只要表达式同时混合多类运算,也应使用括号表达业务意图:
total := price + shipping*quantity
allowed := authenticated && (admin || owner)
与 TypeScript 的关键差异
- Go 没有
===、!==、??、?.和?:条件运算符。 - Go 条件必须是
bool,没有 JavaScript 的真值转换。 - Go 的整数除法仍得到整数,JavaScript 的普通
number除法不采用这一规则。 ++、--和赋值在 Go 中不能嵌入其他表达式。- Go 是否允许
==由静态类型的可比较性规则决定,不等同于 JavaScript 的引用比较模型。
延伸学习
通道运算符的阻塞、关闭和同步行为见 Concurrency(并发)。不要仅根据本篇的单 goroutine 示例推断并发安全性。
小结
- 运算结果由操作数类型决定,整数除法会向零截断。
- 比较结果是
bool,切片、映射和函数不能彼此比较。 &&与||会短路,条件不能依赖隐式真值转换。- 位运算只用于整数,
&^可清除指定比特位。 ++、--和赋值都是语句层面的操作,不能当作值使用。- 复杂表达式应使用括号突出意图,而不是只依赖优先级记忆。