运算符
运算符是用于执行运算的符号,它对一个或多个操作数进行操作,产生一个结果。Go 语言提供了丰富的运算符,包括算术、关系、逻辑、位运算和赋值运算符。
算术运算符
算术运算符用于执行基本的数学运算:
| 运算符 | 描述 | 示例 |
|---|---|---|
+ | 加法 | 3 + 2 = 5 |
- | 减法 | 3 - 2 = 1 |
* | 乘法 | 3 * 2 = 6 |
/ | 除法 | 7 / 2 = 3(整数除法截断小数部分) |
% | 取模(取余) | 7 % 2 = 1 |
++ | 自增(仅后置) | x++ 等价于 x = x + 1 |
-- | 自减(仅后置) | x-- 等价于 x = x - 1 |
a, b := 10, 3
fmt.Println(a + b) // 13
fmt.Println(a - b) // 7
fmt.Println(a * b) // 30
fmt.Println(a / b) // 3(整数除法,截断小数部分)
fmt.Println(a % b) // 1
// 浮点数除法
fmt.Println(10.0 / 3.0) // 3.3333333333333335整数除法与取模
Go 的整数除法是向零截断(truncate toward zero),而不是向下取整。-7 / 2 = -3(不是 -4)。取模运算结果的符号与被除数相同:-7 % 2 = -1。
自增与自减
Go 的 ++ 和 -- 语句与 C/Java 有重要区别:
x := 10
// Go 中 ++ 和 -- 是语句,不是表达式
x++ // 正确:x 变为 11
x-- // 正确:x 变为 10
// 以下用法在 Go 中是非法的:
// y := x++ // 编译错误:++ 不是表达式
// if x++ > 0 {} // 编译错误
// fmt.Println(x++) // 编译错误
// 正确的写法:
x++
if x > 0 {}Go 的 ++ 和 -- 设计哲学
Go 移除了前置的 ++x 和 --x,也不允许将 ++/-- 作为表达式使用。这样设计是为了避免 C 语言中 x = i++ 这类容易混淆的写法,使代码更加清晰。
关系运算符
关系运算符用于比较两个值,返回 bool 结果:
| 运算符 | 描述 | 示例 |
|---|---|---|
== | 等于 | 5 == 5 → true |
!= | 不等于 | 5 != 3 → true |
< | 小于 | 3 < 5 → true |
> | 大于 | 5 > 3 → true |
<= | 小于或等于 | 3 <= 3 → true |
>= | 大于或等于 | 5 >= 5 → true |
a, b := 10, 20
fmt.Println(a == b) // false
fmt.Println(a != b) // true
fmt.Println(a < b) // true
fmt.Println(a > b) // false
fmt.Println(a <= b) // true
fmt.Println(a >= b) // false浮点数比较
不要直接使用 == 比较浮点数。由于浮点精度问题,应使用容差比较(参见”基本数据类型”章节)。
字符串比较
Go 中字符串可以使用关系运算符比较,按字典序(Unicode 码点顺序)比较:
fmt.Println("abc" < "abd") // true
fmt.Println("abc" == "abc") // true
fmt.Println("ABC" < "abc") // true(大写字母的 Unicode 码点小于小写字母)
fmt.Println("你好" < "世界") // true逻辑运算符
逻辑运算符用于操作布尔值:
| 运算符 | 描述 | 示例 |
|---|---|---|
&& | 逻辑与(AND) | true && false → false |
|| | 逻辑或(OR) | true || false → true |
! | 逻辑非(NOT) | !true → false |
a, b := true, false
fmt.Println(a && b) // false
fmt.Println(a || b) // true
fmt.Println(!a) // false
fmt.Println(!b) // true短路求值
Go 的 && 和 || 具有短路求值(Short-circuit evaluation)特性:
// && 短路:如果左侧为 false,右侧不再计算
var result bool
result = false && someExpensiveFunction() // someExpensiveFunction 不会被调用
// || 短路:如果左侧为 true,右侧不再计算
result = true || someExpensiveFunction() // someExpensiveFunction 不会被调用短路求值的应用
短路求值常用于防止空指针panic或避免不必要的计算:
// 利用短路求值防止空指针
if user != nil && user.Name == "Alice" {
fmt.Println("Hello, Alice!")
}
// 如果 user 为 nil,&& 左侧为 false,右侧不会被求值,不会 panic位运算符
位运算符直接对整数的二进制位进行操作:
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 按位与(AND) | 5 & 3 → 1 |
| | 按位或(OR) | 5 | 3 → 7 |
^ | 按位异或(XOR) | 5 ^ 3 → 6 |
&^ | 按位清除(AND NOT) | 5 &^ 3 → 4 |
<< | 左移 | 1 << 3 → 8 |
>> | 右移 | 8 >> 3 → 1 |
按位与(&)
两个对应位都为 1 时结果才为 1:
0101 (5)
& 0011 (3)
-------
0001 (1)fmt.Println(5 & 3) // 1
fmt.Println(0xFF & 0x0F) // 15按位或(|)
两个对应位有一个为 1 时结果就为 1:
0101 (5)
| 0011 (3)
-------
0111 (7)fmt.Println(5 | 3) // 7按位异或(^)
两个对应位不同时结果为 1,相同时为 0:
0101 (5)
^ 0011 (3)
-------
0110 (6)fmt.Println(5 ^ 3) // 6
fmt.Println(5 ^ 5) // 0(相同异或为零)异或的实用技巧
a ^ a = 0:任何数与自身异或结果为零a ^ 0 = a:任何数与零异或结果不变- 交换两个变量:
a, b = b, a(Go 的多重赋值比异或交换更清晰) - 取反特定位:
a ^ 0xFF对低 8 位取反
按位清除(&^)
&^ 是 Go 特有的运算符,也称为”位清除”。对于右侧为 1 的位,将左侧对应位清零;右侧为 0 的位,保持左侧不变:
0101 (5)
&^0011 (3)
-------
0100 (4)fmt.Println(5 &^ 3) // 4
// 解释:右侧 3 的二进制为 0011,第0位和第1位为1,所以清除左侧的第0位和第1位移位运算(<<, >>)
左移 << 将二进制位向左移动指定位数,右边补零;右移 >> 将二进制位向右移动指定位数:
1 << 3:
0001 → 1000 (8)
8 >> 3:
1000 → 0001 (1)fmt.Println(1 << 0) // 1
fmt.Println(1 << 1) // 2
fmt.Println(1 << 2) // 4
fmt.Println(1 << 3) // 8
fmt.Println(1 << 10) // 1024移位运算的常见用途
- 乘以/除以 2 的幂:
x << 3等价于x * 8,x >> 3等价于x / 8 - 定义位标志:
const Read = 1 << 0; const Write = 1 << 1 - 在位运算中,移位通常比乘除法更快
赋值运算符
赋值运算符用于给变量赋值:
| 运算符 | 描述 | 等价写法 |
|---|---|---|
= | 赋值 | x = 5 |
+= | 加后赋值 | x += 5 → x = x + 5 |
-= | 减后赋值 | x -= 5 → x = x - 5 |
*= | 乘后赋值 | x *= 5 → x = x * 5 |
/= | 除后赋值 | x /= 5 → x = x / 5 |
%= | 取模后赋值 | x %= 5 → x = x % 5 |
&= | 按位与后赋值 | x &= 5 → x = x & 5 |
|= | 按位或后赋值 | x |= 5 → x = x | 5 |
^= | 按位异或后赋值 | x ^= 5 → x = x ^ 5 |
<<= | 左移后赋值 | x <<= 2 → x = x << 2 |
>>= | 右移后赋值 | x >>= 2 → x = x >> 2 |
x := 10
x += 5 // x = 15
x -= 3 // x = 12
x *= 2 // x = 24
x /= 4 // x = 6
x %= 4 // x = 2
y := 0b1100 // 12
y &= 0b1010 // y = 8 (1000)
y |= 0b0101 // y = 13 (1101)
y ^= 0b1111 // y = 2 (0010)运算符优先级
Go 的运算符优先级从高到低如下表所示:
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 5 | * / % << >> & &^ | 左结合 |
| 4 | + - | ^ | 左结合 |
| 3 | == != < <= > >= | 左结合 |
| 2 | && | 左结合 |
| 1 | || | 左结合 |
括号优先
当不确定运算符优先级时,使用括号。清晰可读的代码比依赖优先级规则更值得提倡。Go 官方也推荐在复杂表达式中使用括号明确意图。
// 不推荐的写法(依赖优先级)
result := a + b * c
// 推荐的写法(明确意图)
result := a + (b * c)
// 逻辑运算推荐加括号
if (x > 0) && (y > 0) && (z > 0) {
// ...
}练习题
练习 1:位运算判断奇偶
不使用 % 运算符,仅使用位运算判断一个整数是奇数还是偶数。
解题思路:一个整数的二进制最低位决定了它是奇数还是偶数。最低位为 1 则是奇数,为 0 则是偶数。可以用 & 1 来检查最低位。
代码:
package main
import "fmt"
func isOdd(n int) bool {
return n&1 == 1
}
func isEven(n int) bool {
return n&1 == 0
}
func main() {
fmt.Println(isOdd(5)) // true
fmt.Println(isOdd(8)) // false
fmt.Println(isEven(5)) // false
fmt.Println(isEven(8)) // true
fmt.Println(isOdd(-3)) // true(负数同样适用)
}解释:n & 1 只保留 n 的最低位。如果最低位是 1,则 n 是奇数;如果是 0,则 n 是偶数。位运算比取模运算效率更高。
练习 2:位标志操作
使用位运算实现一个简单的权限系统。定义以下权限:读(1)、写(2)、执行(4)。实现添加权限、删除权限、检查权限的功能。
解题思路:使用按位或 | 添加权限,使用按位清除 &^ 删除权限,使用按位与 & 检查权限。
代码:
package main
import "fmt"
const (
Read = 1 << iota // 1(0001)
Write // 2(0010)
Execute // 4(0100)
)
// 添加权限
func addPermission(permissions, perm int) int {
return permissions | perm
}
// 删除权限
func removePermission(permissions, perm int) int {
return permissions &^ perm
}
// 检查是否拥有某个权限
func hasPermission(permissions, perm int) bool {
return permissions&perm == perm
}
func main() {
var perms int = 0
// 添加读和执行权限
perms = addPermission(perms, Read)
perms = addPermission(perms, Execute)
fmt.Printf("权限: %04b\n", perms) // 0101
// 检查权限
fmt.Println("可读:", hasPermission(perms, Read)) // true
fmt.Println("可写:", hasPermission(perms, Write)) // false
fmt.Println("可执行:", hasPermission(perms, Execute)) // true
// 删除执行权限,添加写权限
perms = removePermission(perms, Execute)
perms = addPermission(perms, Write)
fmt.Printf("权限: %04b\n", perms) // 0011
fmt.Println("可读:", hasPermission(perms, Read)) // true
fmt.Println("可写:", hasPermission(perms, Write)) // true
fmt.Println("可执行:", hasPermission(perms, Execute)) // false
}练习 3:逻辑运算与短路求值
以下代码的输出是什么?请解释每一步的执行过程。
package main
import "fmt"
func main() {
x := 5
y := 0
result := x > 3 && y != 0 && x/y > 1
fmt.Println(result)
result2 := x > 3 || x/y > 1
fmt.Println(result2)
}解题思路:利用逻辑运算符的短路求值特性分析执行过程。
输出:
false
true解释:
result := x > 3 && y != 0 && x/y > 1- 先计算
x > 3,结果为true - 短路未触发,继续计算
y != 0,结果为false - 由于
&&左侧为false,触发短路,不会计算x/y > 1,避免了除零错误 - 最终结果:
false
- 先计算
result2 := x > 3 || x/y > 1- 先计算
x > 3,结果为true - 由于
||左侧为true,触发短路,不会计算x/y > 1,同样避免了除零错误 - 最终结果:
true
- 先计算
关键知识点:短路求值不仅可以提高性能,还可以用来防止运行时错误。
