五分钟内你能读懂的一切
Go 的语法糖,少到能在一章里讲完。别的语言你得学一学期语法,Go 你读完这一章,基本就能读懂 90% 的日常代码了 —— 因为它根本没给你多少花样可学。这一章我们把这些「少」一口气过一遍,重点不在记住每个规则,而在体会那种「一个东西只有一种写法」的踏实感。
声明变量:: = 就够了
Go 声明变量有两种写法,但你 90% 的时间只用一种:
var name string = "go" // 完整写法:var 名字 类型 = 值 var age = 10 // 类型能推断,就省掉 name := "go" // 函数内部最常用:: = 短声明,连 var 都省
:= 是 Go 里出现频率最高的符号之一,意思是「声明一个新变量并赋值,类型你(编译器)自己推」。它只能在函数内部用。记住 := 是声明(第一次出现),= 是赋值(改一个已存在的变量)—— 这个区分后面会反复用到。
你注意到类型写在名字后面了吗?name string,不是 string name。这和 C / Java / Kotlin 都不一样(Kotlin 其实也是名字在前 val name: String,但没有冒号)。Go 这么排是有道理的,读复杂类型时从左到右念得通,这个第 5、6 章会再体会。
没有分号,也没有「大括号换行自由」
Go 代码行尾不写分号。(其实语言规范里有分号,但编译器在每行末尾自动帮你补,你永远不用手写。)这带来一个你必须知道的硬性约束:
// ✅ 唯一合法的写法:左大括号必须跟在同一行
func main() {
fmt.Println("hi")
}
// ❌ 编译错误!左大括号不能另起一行
func main()
{
fmt.Println("hi")
}
因为「自动补分号」的规则会在 main() 后面补一个分号,于是函数就没有函数体了。所以在 Go 里,大括号往哪儿放不是风格问题,是语法规定 —— 又一处「删掉选择」:你们团队永远不会为大括号换不换行吵架,因为只有一种是合法的。
只有一个 for,它是全部循环
这是最能代表 Go 减法精神的一处:Go 没有 while,没有 do-while,没有 forEach 关键字。只有一个 for,它一个人演所有循环的角色:
// 1. 经典三段式(和 Java 一样)
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 2. 去掉前后两段 = while
for x < 100 {
x *= 2
}
// 3. 什么都不写 = 死循环(配 break / return 退出)
for {
if done() {
break
}
}
// 4. range:遍历切片 / map / 字符串 / channel
for i, v := range items {
fmt.Println(i, v)
}
四种形态,一个关键字。你想写 while (x < 100) 时,Go 让你写 for x < 100。一开始你的手会去够 while,够不到;用几天,你会发现「反正都是循环,凭什么要三个不同的词」这个质问其实挺有道理。这就是卸载的手感 —— 先别扭,再释然。
for i, v := range items 里的 v 是每个元素的一份复制,不是元素本身。改 v 不会改到原切片。这是 Go「默认复制」性格的又一次出现,第 5 章会专门讲这套值语义,到时你会明白它为什么这么设计。
if 能带一句初始化,但没有三元
Go 的 if 有个好用的小设计:条件前面可以塞一句初始化,那个变量的作用域只在这个 if 里:
// v 只在这个 if / else 里可见,出去就没了
if v, ok := cache[key]; ok {
use(v)
} else {
fmt.Println("没这个 key")
}
这个 if x := ...; cond 的形态你会天天见到,尤其配错误处理(if err := do(); err != nil)。它把变量的作用域收得很紧 —— 又是「让读的人少操心」的设计。
但 Go 没有三元表达式。你不能写 x := a > b ? a : b。想要就老老实实写 if:
max := a
if b > a {
max = b
}
四行,换 Kotlin 一行。Go 团队的理由还是那个:三元表达式嵌套起来(a ? b : c ? d : e)是可读性的噩梦,与其给你一个能被滥用的简写,不如干脆不给,逼你写成谁都看得懂的 if。你可以不同意,但你得承认这个理由是自洽的。
switch:不用 break,能拿它当 if 链用
switch grade {
case "A":
fmt.Println("优秀") // 自动 break,不会掉进下一个 case
case "B", "C": // 一个 case 可以列多个值
fmt.Println("还行")
default:
fmt.Println("加油")
}
// 不给 switch 条件 = 一条更清爽的 if-else 链
switch {
case score >= 90:
fmt.Println("A")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("挂了")
}
两处减法:一是 Go 的 case 默认自动 break,不会像 C / Java 那样「忘了写 break 就掉进下一个 case」—— 那个坑被删了(想故意穿透,得显式写 fallthrough,几乎没人用)。二是 switch 不带条件时,等于一条 if-else 链,比连着写 else if 清爽。后面第 11 章还有一种特别的「类型 switch」,是 Go 处理接口的关键工具,先记住 switch 这个形状。
函数:可以返回多个值
// 返回两个值:结果 + 错误。这是 Go 最标志性的函数签名
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除以零")
}
return a / b, nil
}
// 调用:用两个变量接住
result, err := divide(10, 2)
「返回多个值」是 Go 一个不起眼但影响深远的能力。正因为函数能同时返回「结果」和「错误」,Go 才敢删掉异常机制(第 14 章)—— 错误不用抛,直接当第二个返回值给你。你还会常看到 value, ok := m[key] 这种「值 + 是否存在」的双返回,Go 管它叫 comma-ok 惯用法。
Go 没有 public / private / internal 这些修饰符。一个名字首字母大写,它就是导出的(包外可见,相当于 public);首字母小写,就是包私有的(相当于 private)。
fmt.Println 的 P 大写,所以你能调;fmt 包里那些小写开头的函数,你在包外根本看不见。这条规则统治一切 —— 函数、类型、字段、变量、方法全都这样。于是「这个东西对外可见吗」这个问题,看首字母就知道,不用去翻声明处有没有修饰符。又一次:把信息摊到明面上,省掉读者的一次跳转。
常量与 iota
const Pi = 3.14159
// iota:在一组常量里自动递增,用来造枚举
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)
Go 没有真正的枚举类型(又一处「没有」)。造枚举的惯用法就是 const + iota:iota 在每个 const 组里从 0 开始,每行自动加 1。这比 Kotlin 的 enum class 简陋得多 —— 它就是一组整数常量,没有方法、没有穷尽检查。想要「像样的枚举」,Go 的答案是别的东西(自定义类型 + 方法,第 8 章会讲)。
你现在已经能读懂大半 Go 代码了
回头数数这一章讲的东西::=、一个 for、带初始化的 if、自动 break 的 switch、多返回值、大小写可见性、const/iota。这几乎就是 Go 的全部控制流和声明语法了。没有了?没有了。不是我省略了,是 Go 真的就这么点东西。
这就是「少」的回报:你花五分钟读完这一章,就能看懂绝大多数 Go 代码的骨架。一门语言把自己缩到这个程度,是需要极大克制的 —— 每一个「我们不加这个特性」的决定背后,都有一群工程师想加、被劝住了。
Kotlin 对照
你会明确想念这些:三元表达式(a ?: b 这种爽快的简写全没了)、when 作为表达式返回值、字符串模板 "$name"(Go 得用 fmt.Sprintf("%s", name))、以及 Kotlin 那种「一个函数一个表达式」的极简。头两周你会觉得 Go 让你多打好多字。
但有几样你会悄悄松一口气:再没有「忘写 break 掉进下个 case」的 bug;再没有为大括号换行、缩进风格开的会;再不用记「这个循环该用 for 还是 while 还是 forEach」。Go 把一堆需要做选择的地方直接删成了「只有一种」,你少了很多微小的决策疲劳。
一句话:Kotlin 给你很多好用的表达式,Go 给你很少但很稳的语句。前者写着爽,后者读着省心。
这一章的一句话
Go 的语法糖少到能一次讲完:一个 for 演所有循环、switch 自动 break、if 能带初始化但没有三元、大小写就是可见性、函数能返回多个值 —— 每一处「少」都是把「需要做选择的地方」删成了「只有一种写法」。
下一章,我们看 Go 怎么组织代码:包、模块,以及那个让你一开始很火大、后来很感激的规矩 —— 未使用的变量和 import 是编译错误。