运算符:Go语言的“计算器”与“裁判员”
运算符这东西,说白了就是让程序帮你做判断和计算的工具。Go语言的运算符分四类:算术、比较、逻辑、位运算。咱们一个一个聊。
算术运算符:加减乘除,还有取模
算术运算符跟数学课上学的差不多。加+、减-、乘*、除/、取模%。嗯,这里要注意,Go是强类型语言,不同类型不能直接运算。
package main
import "fmt"
func main() {
a := 10
b := 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,取余数
}
⚠️ 避坑指南
我曾经在写一个支付系统时,直接用整数除法算单价,结果金额全错了。记住:
我曾经在写一个支付系统时,直接用整数除法算单价,结果金额全错了。记住:
10 / 3 在Go里等于 3,不是 3.333。想要小数,得用浮点数。
比较运算符:谁大谁小,一目了然
比较运算符返回布尔值,true 或 false。包括:==、!=、<、>、<=、>=。
package main
import "fmt"
func main() {
x := 5
y := 8
fmt.Println(x == y) // false
fmt.Println(x != y) // true
fmt.Println(x < y) // true
fmt.Println(x > y) // false
}
💡 个人习惯
我写条件判断时,习惯把常量放左边,比如
我写条件判断时,习惯把常量放左边,比如
0 == x 而不是 x == 0。这样万一少写一个等号变成赋值,编译器会报错。这招是从C语言时代带过来的老毛病,但确实管用。
逻辑运算符:与或非,组合判断
逻辑运算符有三个:&&(与)、||(或)、!(非)。它们有短路特性——能提前确定结果就不往后算了。
package main
import "fmt"
func main() {
age := 25
hasID := true
// 与:两个都满足才行
if age >= 18 && hasID {
fmt.Println("可以进入")
}
// 或:满足一个就行
if age < 12 || hasID == false {
fmt.Println("需要家长陪同")
}
// 非:取反
if !hasID {
fmt.Println("没带证件")
}
}
🔑 关键点
短路求值:
短路求值:
a && b 如果 a 是 false,b 根本不会执行。我在项目中遇到过因为依赖短路特性来避免空指针,结果重构时顺序搞反了,直接panic。所以依赖短路要谨慎。
位运算符:底层操作的利器
位运算直接操作二进制位。包括:&(按位与)、|(按位或)、^(按位异或)、<<(左移)、>>(右移)。
package main
import "fmt"
func main() {
a := 5 // 二进制 0101
b := 3 // 二进制 0011
fmt.Println(a & b) // 1 (0001)
fmt.Println(a | b) // 7 (0111)
fmt.Println(a ^ b) // 6 (0110)
fmt.Println(a << 1) // 10 (1010),左移一位相当于乘以2
fmt.Println(a >> 1) // 2 (0010),右移一位相当于除以2
}
💡 实战场景
位运算在权限系统里特别好用。比如用
位运算在权限系统里特别好用。比如用
1 << 0 表示读权限,1 << 1 表示写权限,1 << 2 表示执行权限。组合起来就是一个整数,检查权限时用 & 运算就行。我在做容器权限管理时就这么干的,性能好,代码也干净。
流程控制:让代码学会“思考”
if 语句:最简单的分支
Go的 if 不需要括号,但必须有大括号。而且可以在条件前加一个简单语句。
package main
import "fmt"
func main() {
score := 85
// 基本用法
if score >= 90 {
fmt.Println("优秀")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
// 带初始化语句的if
if age := 20; age >= 18 {
fmt.Println("成年人")
}
// 这里 age 已经不可见了,作用域只在if块内
}
⚠️ 避坑指南
我曾经见过有人把
我曾经见过有人把
if err != nil 写成 if err = nil,少了一个感叹号,结果赋值成功了,条件永远为假。Go编译器不会报错,但逻辑全错了。所以写条件判断时,多看一眼。
switch 语句:多路分支的优雅写法
Go的 switch 不需要 break,默认只执行一个分支。而且可以匹配表达式、类型,甚至空着当 if-else 用。
package main
import "fmt"
func main() {
day := "周一"
// 基本switch
switch day {
case "周一", "周二", "周三", "周四", "周五":
fmt.Println("工作日")
case "周六", "周日":
fmt.Println("周末")
default:
fmt.Println("无效日期")
}
// 无表达式的switch,相当于if-else链
score := 75
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("D")
}
}
🔑 关键点
Go的
Go的
switch 默认不会穿透。如果你想让一个分支继续执行下一个,得用 fallthrough。但说实话,我很少用 fallthrough,因为它容易让逻辑变得混乱。你想想看,本来每个分支是独立的,突然来个穿透,读代码的人得停下来想半天。
for 循环:Go里唯一的循环
Go只有 for 循环,没有 while 和 do-while。但 for 有三种写法,能覆盖所有场景。
package main
import "fmt"
func main() {
// 标准for循环
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 相当于while的写法
sum := 0
for sum < 10 {
sum += 2
}
fmt.Println(sum) // 10
// 无限循环
count := 0
for {
count++
if count > 3 {
break
}
}
// range遍历
nums := []int{10, 20, 30}
for index, value := range nums {
fmt.Printf("索引%d的值是%d\n", index, value)
}
}
💡 个人经验
用
用
range 遍历时,如果不需要索引,用 _ 占位。比如 for _, v := range slice。我见过有人写 for i, v := range slice 然后 i 从来没用到,编译器会报错。Go对未使用的变量零容忍,这是好事。
defer 语句:延迟执行,资源清理的利器
defer 是Go里一个很特别的设计。它会把函数调用压入一个栈,等外层函数返回前再依次执行。说白了就是「走之前把事办了」。
package main
import "fmt"
func main() {
defer fmt.Println("世界")
fmt.Println("你好")
// 输出:
// 你好
// 世界
}
🔑 关键点
defer 的执行顺序是后进先出(LIFO)。如果你写了多个 defer,最后一个会最先执行。这跟栈的行为一样。
defer 的典型用法:资源管理
最常见的场景是关闭文件、解锁互斥锁、关闭数据库连接。把 defer 紧跟在打开资源后面,代码清晰,也不容易忘记释放。
package main
import (
"fmt"
"os"
)
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer file.Close() // 函数返回前自动关闭
// 读取文件内容...
fmt.Println("文件已打开")
}
func main() {
readFile("test.txt")
}
⚠️ 避坑指南
我曾经在循环里用
我曾经在循环里用
defer 关闭文件,结果循环了1000次,所有 defer 都等到函数返回才执行。文件描述符全被占着,系统直接报 too many open files。记住:defer 是在函数返回时执行,不是在循环结束时。循环里别用 defer,除非你清楚后果。
defer 与参数求值
defer 的参数在声明时就会求值,而不是在真正执行时。这个细节很多人会搞错。
package main
import "fmt"
func main() {
x := 10
defer fmt.Println(x) // 这里x的值是10,已经确定了
x = 20
fmt.Println(x) // 输出20
// defer输出10,不是20
}
💡 实战技巧
如果你想让
如果你想让
defer 使用变量最终的值,可以用闭包。比如 defer func() { fmt.Println(x) }()。这样 x 会在 defer 真正执行时才取值。我在写日志记录时经常用这个技巧,记录函数执行后的状态。
defer 与返回值
defer 可以修改命名返回值。这个特性在错误处理时特别有用。
package main
import "fmt"
func divide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("发生了panic: %v", r)
}
}()
result = a / b
return
}
func main() {
r, err := divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", r)
}
}
🔑 关键点
defer 可以读取和修改命名返回值。这在做统一错误处理、记录执行时间、捕获 panic 时非常有用。我个人习惯在函数开头写一个 defer 来做收尾工作,这样函数主体可以专注于业务逻辑,不用操心清理的事。
好了,运算符和流程控制就聊到这儿。这些是Go编程的基石,写多了自然就熟练了。下一章咱们聊聊函数和包管理,那才是真正开始组织代码的地方。