本节摘要:本节覆盖 Go 的最小必备语法集:变量与常量的声明方式、基本数据类型与零值、for 与 switch 的控制流写法,以及强制使用的规则。学完本节,你能写出通过编译的日常逻辑代码,并理解 Go 编译器那些"看起来苛刻"的规定背后的工程理由。
环境就绪,本节把操作规程过一遍。规程类知识的特点是条文枯燥、违反必罚,所以本节先从一个"编译不过"的反例开始——先知道哪些动作会被罚,再学正确姿势,印象会深得多。这些语法是后面所有章节的通用语言,第 3 章的并发示例一行都绕不开它们。
package main import ( "fmt" "strings" // 导入了但没用 ) func main() { x := 1 y := 2 // 声明了但没用 fmt.Println("hello") x = 3 // x 用上了,这句没问题 } // 编译器拒绝通过,报错大意: // y declared and not used // imported and not used: "strings"
在其他语言里,未使用的变量多半只是个警告;在 Go 里它是编译错误。这不是较真——大型项目里"声明了没用的变量"往往是残缺逻辑或改名残留,编译期拦下比留到线上便宜太多。理解了这一点,再看变量声明就顺了。
package main import "fmt" // 包级变量只能用 var 声明,不能用 :=(短声明仅限函数内部) var version = "v1.0" func main() { var retries int = 3 // 完整声明:var 名 类型 = 值 var timeout = 30 // 类型推断:省略类型,由右侧决定 worker := "night-shift" // 短声明:最常用,只能出现在函数内 const maxJobs = 100 // 常量:编译期确定,不可再赋值 fmt.Println(version, retries, timeout, worker, maxJobs) } // 运行输出: // v1.0 3 30 night-shift 100
三条选用经验:函数内优先用短声明;需要零值初始化或包级作用域时用 var;不会变的量一律 const。Go 的另一个贴心设定是零值:声明但不赋值的变量自动获得该类型的零值(数字为 0、布尔为 false、字符串为空串、指针为 nil),不存在"未初始化垃圾值"这类隐性炸弹。
常量搭配 iota 能生成有规律的序列,枚举场景很常见:
type Level int const ( Debug Level = iota // 0,iota 在每个 const 块首行从 0 自增 Info // 1,省略表达式即沿用上一行 Warn // 2 Error // 3 ) // 用法:log 级别、任务优先级等有限有序集合
Go 只有一个循环关键字 for,但它是变形金刚,覆盖了其他语言三种循环的用途:
package main import "fmt" func main() { // 形态一:经典三段式 for i := 0; i < 3; i++ { fmt.Print(i, " ") } fmt.Println() // 形态二:只有条件,相当于其他语言的 while pending := 8 for pending > 1 { pending /= 2 } fmt.Println("剩余待办:", pending) // 形态三:无限循环,配 break 退出 count := 0 for { count++ if count >= 3 { break } } fmt.Println("循环了", count, "次") // 形态四:range 遍历集合,同时拿到下标与值 tasks := []string{"备份", "巡检", "报表"} for idx, task := range tasks { fmt.Println(idx, task) } } // 运行输出: // 0 1 2 // 剩余待办: 1 // 循环了 3 次 // 0 备份 // 1 巡检 // 2 报表
条件分支里,switch 比 C 家族安全:默认不穿透(不用写 break),还能不带条件当"开关板"用:
func grade(score int) string { switch { case score >= 90: return "优" case score >= 60: return "及格" default: return "补考" } } // grade(95) 得 "优";grade(61) 得 "及格";grade(40) 得 "补考" // 多个命中条件只会走第一个 case,顺序即优先级
背景:值班系统收到一批工单,需要按紧急程度分级:超时两小时以上的插队处理,带告警关键字的升级上报,其余按先来后到排队。
操作:用 switch 开关板写分级函数,外层用 range 遍历工单切片,统计各等级数量。
package main import ( "fmt" "strings" ) func classify(waitMinutes int, title string) string { switch { case waitMinutes > 120: return "插队" case strings.Contains(title, "告警"): return "上报" default: return "排队" } } func main() { tickets := []struct { mins int title string }{ {150, "磁盘清理"}, {30, "数据库告警"}, {45, "文档更新"}, } stats := map[string]int{} for _, t := range tickets { level := classify(t.mins, t.title) stats[level]++ fmt.Println(t.title, "->", level) } fmt.Println("分级统计:", stats) } // 运行输出: // 磁盘清理 -> 插队 // 数据库告警 -> 上报 // 文档更新 -> 排队 // 分级统计: map[上报:1 排队:1 插队:1]
结果:三张工单各归其位,统计 map 输出与预期一致。
解读:struct 切片加 range 是 Go 处理批量记录的惯用组合;map 的零值可直接写入,不用先初始化,这继承自零值设计。switch 的 case 顺序就是业务优先级,读代码的人一眼能看到裁决规则。
变式:把分级规则改成"超时且带告警才上报",用 if 写出嵌套两层;再用 switch 复原。对比后你会发现开关板写法在这种多维裁决里更平——这就是 1.4 节要继续深挖的"用结构组织逻辑"。
x = i++ 的写法,杜绝了歧义。语法会用了,下一节解决"代码放哪儿、怎么分家":函数的细节与包的组织。