Go 语言运算符与表达式详解
Go 语言提供了丰富的运算符和表达式类型,用于执行各种计算和逻辑操作。下面我将全面介绍 Go 中的运算符和表达式系统。
一、运算符分类
1. 算术运算符
运算符 | 描述 | 示例 | 备注 |
+ | 加法 | a + b | 也可用于字符串连接 |
- | 减法 | a - b | |
* | 乘法 | a * b | |
/ | 除法 | a / b | 整数除法会截断小数部分 |
% | 取模 | a % b | 仅用于整数 |
++ | 自增 | a++ | 只有后置形式,不是表达式 |
-- | 自减 | a-- | 只有后置形式,不是表达式 |
特殊案例:
fmt.Println(5 / 2) // 2 (整数除法)
fmt.Println(5 / 2.0) // 2.5 (浮点除法)
fmt.Println(10 % 3) // 1
2. 关系运算符
运算符 | 描述 | 示例 |
== | 等于 | a == b |
!= | 不等于 | a != b |
< | 小于 | a < b |
<= | 小于等于 | a <= b |
> | 大于 | a > b |
>= | 大于等于 | a >= b |
注意:比较运算符返回 bool 值
3. 逻辑运算符
运算符 | 描述 | 示例 | 短路特性 |
&& | 逻辑与 | a && b | 左假则右不计算 |
` | ` | 逻辑或 | |
! | 逻辑非 | !a |
短路示例:
func isEven(n int) bool {
fmt.Println("called")
return n%2 == 0
}
if false && isEven(2) { // isEven不会被调用
// ...
}
4. 位运算符
运算符 | 描述 | 示例 | 说明 |
& | 按位与 | a & b | 对应位都为1则为1 |
` | ` | 按位或 | `a |
^ | 按位异或 | a ^ b | 对应位不同则为1 |
&^ | 位清除 | a &^ b | 将b中1的位在a中置0 |
<< | 左移 | a << n | 左移n位,低位补0 |
>> | 右移 | a >> n | 右移n位,高位补符号位 |
位运算案例:
a := 0b1010 // 10
b := 0b1100 // 12
fmt.Printf("%04b\n", a&b) // 1000 (8)
fmt.Printf("%04b\n", a|b) // 1110 (14)
fmt.Printf("%04b\n", a^b) // 0110 (6)
fmt.Printf("%04b\n", a&^b) // 0010 (2)
fmt.Printf("%04b\n", a<<2) // 101000 (40)
fmt.Printf("%04b\n", b>>1) // 0110 (6)
5. 赋值运算符
运算符 | 描述 | 示例 | 等价于 |
= | 简单赋值 | a = b | |
+= | 加后赋值 | a += b | a = a + b |
-= | 减后赋值 | a -= b | a = a - b |
*= | 乘后赋值 | a *= b | a = a * b |
/= | 除后赋值 | a /= b | a = a / b |
%= | 取模赋值 | a %= b | a = a % b |
&= | 位与赋值 | a &= b | a = a & b |
` | =` | 位或赋值 | `a |
^= | 位异或赋值 | a ^= b | a = a ^ b |
<<= | 左移赋值 | a <<= n | a = a << n |
>>= | 右移赋值 | a >>= n | a = a >> n |
6. 其他运算符
运算符 | 描述 | 示例 | 说明 |
& | 取地址 | &a | 获取变量内存地址 |
* | 指针解引用 | *ptr | 获取指针指向的值 |
<- | 通道操作 | ch <- x | 发送或接收数据 |
: | 切片/数组定义 | [n:m] | 切片操作 |
... | 可变参数/展开 | func(a ...int) | 函数参数或切片展开 |
二、运算符优先级
从高到低排列:
优先级 | 运算符 |
1 | () [] -> . ++ -- |
2 | + - ! ^ * & <- |
3 | * / % << >> & &^ |
4 | + - ` |
5 | == != < <= > >= |
6 | && |
7 | ` |
示例:
a := 5 + 3 * 2 // 11 (乘法优先)
b := (5 + 3)*2 // 16 (括号优先)
三、表达式类型
1. 基本表达式
变量/常量引用:
x := 42
y := x + 10
字面量表达式:
3.14 // 浮点数字面量
"hello" // 字符串字面量
[]int{1,2,3} // 切片字面量
2. 组合表达式
算术表达式:
area := width * height
avg := (a + b + c) / 3.0
逻辑表达式:
if age >= 18 && hasLicense {
// ...
}
位运算表达式:
flags := read | write | execute
mask := flags &^ disable
3. 函数调用表达式
// 简单调用
sum := add(a, b)
// 方法调用
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
// 延迟调用
defer file.Close()
4. 类型断言表达式
var i interface{} = "hello"
s, ok := i.(string) // ok为true
n, ok := i.(int) // ok为false
5. 选择表达式
结构体字段选择:
type Point struct{X, Y int}
p := Point{1, 2}
x := p.X
方法选择:
var buf bytes.Buffer
buf.WriteString("hello")
6. 索引表达式
数组/切片索引:
arr := [3]int{1, 2, 3}
val := arr[1] // 2
映射索引:
m := map[string]int{"a": 1}
v, ok := m["a"] // v=1, ok=true
7. 切片表达式
s := []int{0,1,2,3,4}
s1 := s[1:3] // [1,2]
s2 := s[:4] // [0,1,2,3]
s3 := s[2:] // [2,3,4]
s4 := s[:] // [0,1,2,3,4]
8. 类型转换表达式
i := 42
f := float64(i)
s := string(rune(i)) // 注意:不是数字转字符串
b := []byte("hello")
四、特殊运算符详解
1. 位清除运算符 &^
作用:将右操作数中为1的位,在左操作数中对应位置0
示例:
a := 0b10101111
b := 0b00001111
c := a &^ b // 10100000 (160)
实际应用:权限系统
const (
Read = 1 << iota // 0001
Write // 0010
Execute // 0100
)
func revokePermission(current, perm int) int {
return current &^ perm
}
perms := Read | Write // 0011
perms = revokePermission(perms, Write) // 0001
2. 通道运算符 <-
发送数据:
ch := make(chan int, 1)
ch <- 42 // 发送值到通道
接收数据:
value := <-ch // 从通道接收值
作为类型:
var inChan <-chan int // 只读通道
var outChan chan<- int // 只写通道
3. 指针运算符 * 和 &
取地址:
x := 42
ptr := &x // 获取x的地址
解引用:
y := *ptr // 获取ptr指向的值
*ptr = 100 // 修改ptr指向的值
五、运算符重载与限制
Go 不支持运算符重载,这是设计上的选择:
- 保持代码清晰性
- 避免滥用导致的复杂行为
- 所有运算符行为都是固定的
替代方案:
- 使用方法代替运算符重载
- type Vector struct{X, Y int} func (v Vector) Add(other Vector) Vector { return Vector{v.X + other.X, v.Y + other.Y} }
- 实现特定接口(如 Stringer)
- func (v Vector) String() string { return fmt.Sprintf("(%d,%d)", v.X, v.Y) }
六、表达式求值规则
1. 求值顺序
Go 保证:
- 赋值语句从左到右求值
- 函数参数和索引表达式从左到右求值
- 运算符优先级决定运算顺序
示例:
a := 1
f := func() int { a++; return a }
b := a + f() // 可能是2或3,取决于求值顺序
2. 短路求值
逻辑运算符 && 和 || 会短路:
func isEven(n int) bool {
fmt.Println("called")
return n%2 == 0
}
if false && isEven(2) { // isEven不会被调用
// ...
}
七、实战应用案例
1. 位运算实现标志位
const (
FlagA = 1 << iota // 0001
FlagB // 0010
FlagC // 0100
FlagD // 1000
)
func setFlag(flags, flag int) int {
return flags | flag
}
func clearFlag(flags, flag int) int {
return flags &^ flag
}
func hasFlag(flags, flag int) bool {
return flags&flag != 0
}
func main() {
var flags int
flags = setFlag(flags, FlagA|FlagC)
fmt.Printf("%04b\n", flags) // 0101
flags = clearFlag(flags, FlagA)
fmt.Printf("%04b\n", flags) // 0100
fmt.Println(hasFlag(flags, FlagB)) // false
fmt.Println(hasFlag(flags, FlagC)) // true
}
2. 安全类型转换工具函数
func ToInt(s string, defaultValue int) int {
if n, err := strconv.Atoi(s); err == nil {
return n
}
return defaultValue
}
func ToFloat(s string, defaultValue float64) float64 {
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
}
return defaultValue
}
3. 表达式解析器
func calculate(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
default:
return 0, fmt.Errorf("unknown operator %q", op)
}
}
Go 语言的运算符和表达式系统设计简洁而强大,虽然不支持运算符重载等复杂特性,但通过合理的组合使用,完全可以满足各种编程需求。理解运算符的优先级和结合性,掌握各种表达式的用法,是编写高效 Go 代码的基础。