大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
基础语法与类型
这一课你将能做到
- 用
var、:=、const三种方式声明变量和常量,并说出每个类型的零值 - 熟练使用 if / for / switch 三种控制流,包括 Go 特有的「初始化语句」写法
- 编写带多返回值、命名返回值和可变参数的函数
为什么要学
任何程序都是「数据 + 流程」。这一课把 Go 的数据怎么声明、流程怎么控制一次讲透。Go 语法刻意做减法:循环只有 for 一种,分支只有 if 和 switch,没有 do-while,也没有三元运算符。花 35 分钟掌握这一课,就能读懂 90% 的 Go 控制流代码,这也是后面切片、接口、并发一切内容的地基。
从 Java/JS 转过来的初学者会踩三个坑:声明的变量不用会编译报错;类型转换必须显式写;i++ 是语句不是表达式。这三个点下面都会遇到。
先建立图像
package main
import "fmt"
const (
StatusOK = iota + 200 // 200,iota 是 const 块的行号,从 0 开始
StatusFound // 201,自动延续上一行的表达式
)
func main() {
count := 10 // := 短声明,类型自动推断为 int
var ratio float64 // 零值 0
if count > 5 && ratio == 0 {
fmt.Println("count 偏大,ratio 未设置")
}
for i := 0; i < count; i++ {
if i%3 == 0 {
continue
}
}
switch n := count / 3; n {
case 1, 2:
fmt.Println("小")
default:
fmt.Println("大:", n)
}
fmt.Println(StatusOK, StatusFound) // 200 201
}
这一张图就是本课全部:声明、零值、if/for/switch、常量、iota。
跟着做
下面每步都可以放进同一个 go mod init example.com/syntax 的项目里运行。
步骤 1:变量声明的三种方式
package main
import "fmt"
func main() {
// 方式一:var 完整声明,可批量
var a int = 10
var b, c = 20, "三十"
// 方式二:var 省略类型,靠右值推断
var d = 3.14
// 方式三:短声明 :=,只能在函数内部使用
e := "hello"
fmt.Println(a, b, c, d, e)
}
规则:包级别只能用 var(:= 不可用),函数内推荐 :=。
步骤 2:零值——Go 没有「未初始化变量」
任何变量声明后不赋值,Go 自动给它确定的「零值」:
package main
import "fmt"
func main() {
var i int // 0
var f float64 // 0
var s string // ""(空串,不是 null!)
var b bool // false
var arr [3]int // [0 0 0]
var p *int // nil
var sl []int // nil(切片零值可直接 append)
var m map[string]int // nil(零值 map 只能读,写入会 panic)
fmt.Println(i, f, b, arr, p, sl, m)
fmt.Printf("%q\n", s) // "",用 %q 看清字符串边界
}
零值设计让很多类型「声明即可用」,不需要先初始化再使用。
步骤 3:基本类型一览
package main
import "fmt"
func main() {
i := 42 // int:位宽随平台(64 位系统上是 64 位)
i64 := int64(42) // 明确位宽:int8/16/32/64
f := 3.14 // float64 是默认浮点;没有叫 float 的类型
s := "golang" // 字符串:不可变的 UTF-8 字节序列
r := '中' // rune = int32 别名,表示一个 Unicode 码点
b := true
by := byte('A') // byte = uint8 别名
fmt.Printf("%T %T %T %T %T %T\n", i, i64, f, s, r, by)
fmt.Println(b)
}
关键规矩:不同类型不能直接运算,必须显式转换。
var a int = 10
var b int64 = 20
// total := a + b // 编译错误:mismatched types int and int64
total := a + int(b) // 正确:显式转换
fmt.Println(total)
步骤 4:常量与 iota
package main
import "fmt"
type Weekday int // 给 int 起名,让枚举有类型含义
const (
Sunday Weekday = iota // 0
Monday // 1,iota 每行自动 +1
Tuesday // 2
Wednesday
Thursday
Friday
Saturday // 6
)
const (
KB = 1 << (10 * (iota + 1)) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
)
func main() {
fmt.Println(Sunday, Wednesday, Saturday)
fmt.Println(KB, MB, GB)
}
iota 是每个 const 块内的行号计数器,从 0 开始,每换一行 +1。常量必须是编译期可确定的值,不能依赖运行时算出来的变量。
步骤 5:if——可以带初始化语句
package main
import (
"fmt"
"strconv"
)
func main() {
// n 和 err 的作用域仅限 if/else 块内——这是 Go 最常见的惯用法
if n, err := strconv.Atoi("42"); err != nil {
fmt.Println("解析失败:", err)
} else {
fmt.Println("解析成功:", n*2)
}
}
条件不需要括号,但左大括号必须同行。
步骤 6:for——唯一的循环
package main
import "fmt"
func main() {
// 形态一:经典三段式
for i := 0; i < 3; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// 形态二:只有条件,替代 while
n := 0
for n < 3 {
n++
}
// 形态三:无限循环,配合 break 退出
count := 0
for {
count++
if count >= 5 {
break
}
}
fmt.Println(n, count)
}
遍历切片和 map 的 for range 形态在第 3 课讲。
步骤 7:switch——默认自动 break
package main
import (
"fmt"
"time"
)
func main() {
// 带初始化语句、省略条件表达式:相当于 switch true
switch h := time.Now().Hour(); {
case h < 12:
fmt.Println("上午")
case h < 18:
fmt.Println("下午")
default:
fmt.Println("晚上")
}
// case 可以并列多个值;需要穿透时显式写 fallthrough(慎用)
switch 2 {
case 1, 2, 3:
fmt.Println("命中 1/2/3")
fallthrough // 无条件进入下一个 case,不再判断它的条件
case 4:
fmt.Println("被 fallthrough 拖进来")
}
}
「初始化 + 空条件」的 switch 是比 if-else 长链更清晰的写法,Go 项目里非常常见。
步骤 8:函数——多返回值是 Go 的标志
package main
import (
"errors"
"fmt"
)
// 多返回值:结果 + 错误,这是 Go 函数的标准签名
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// 命名返回值:直接给返回变量赋值,裸 return 自动返回
func swap(a, b int) (x, y int) {
x, y = b, a
return
}
// 可变参数:nums 在函数内是 []int
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
q, err := divide(10, 3)
fmt.Println(q, err)
_, err2 := divide(1, 0)
fmt.Println(err2)
fmt.Println(swap(1, 2))
fmt.Println(sum(1, 2, 3))
nums := []int{4, 5, 6}
fmt.Println(sum(nums...)) // 切片展开为可变参数
}
讲清楚要点
- 未使用的局部变量是编译错误,未使用的 import 同样报错。这是 Go 强制「代码即真实」的手段;显式丢弃用空白标识符
_。 :=是声明 + 赋值,只能用在函数内;=是纯赋值。同一作用域再写:=时,左侧至少要有一个新变量。- 没有隐式类型转换:int 与 int64、int 与 float64 混算都必须手动转,编译器绝不替你猜,杜绝了隐式截断。
- 循环只有 for;
i++是语句不是表达式,不能写x = i++。 - switch 自动 break,case 值可以逗号并列;
fallthrough很少用,出现时应能给出充分理由。 (T, error)是 Go 的标准函数签名:出错时 T 返回零值、error 非 nil;成功时 error 为 nil。
你可能会懵的地方
-
int 到底是多少位?和 int64 一样吗? int 的位宽跟随平台(64 位机上是 64 位),但它是独立类型,与 int64 混算仍需转换。日常优先用 int;只有序列化、数据库字段等需要明确位宽时用 int64。
-
rune 和 byte 有什么区别? byte 是 uint8(1 字节),rune 是 int32(1 个 Unicode 码点)。
len("中")是 3(3 个字节),len([]rune("中"))才是 1(1 个字符)。字符串细节在第 3 课展开。 -
为什么 Go 没有三元运算符
?:? 官方理由:三元表达式容易嵌套成天书。Go 宁愿多写三行 if-else,也要保证扫一眼就懂。 -
常量为什么必须是编译期确定的? 常量在编译阶段就替换进代码,不是存在内存里的运行时实体。运行时才算得出的值请用变量。
-
iota 能跳号或参与运算吗? 能。中间插一行
_ = iota即可跳号;表达式如iota * 10、1 << iota会自动被后续行复用,只改 iota 值。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
声明 x := 5 后不使用 | declared and not used 编译错误 | 用掉、删除,或用 _ 显式丢弃 |
包级变量写 x := 10 | := 不能在函数外用 | 包级用 var x = 10 |
{ 换到下一行 | 自动分号导致语法错误 | 左大括号必须与语句同行 |
var b float64 = a(a 是 int) | 无隐式转换,编译错误 | float64(a) 显式转换 |
| 期望 switch case 顺序执行 | 默认自动 break,逻辑不执行 | 需要穿透显式写 fallthrough |
int 与 int64 直接相加 | mismatched types | 转换为同一类型再运算 |
卡点急救
declared and not used:删除变量、用_接收,或真的使用它;也检查是否把=误写成:=造成重复声明。cannot use ... as ...(类型不匹配):做显式类型转换,把操作数统一成同一类型。undefined: xxx:拼写错误,或变量定义在 if/switch 的初始化语句里、出了块就失效。
面试常问
-
Go 的零值机制有什么好处? 任何类型都有确定的零值,没有「未初始化」状态,杜绝了读野值和空指针这类问题;
var mu sync.Mutex、var buf bytes.Buffer声明即可用,代码更简洁。 -
:=和var的区别与选择?:=短声明仅限函数内,要求左侧至少一个新变量;包级必须var或const。惯例:函数内用:=,需要显式类型或包级用var。 -
iota 的原理和典型用途? const 块内的行号计数器,从 0 起每行自增,可被表达式复用。典型用途是类型化枚举和位掩码(
1 << iota),如文件权限、日志级别定义。 -
为什么 Go 强制显式类型转换? 把类型边界写进代码,避免隐式截断/溢出;也让评审时数据流向一目了然。
自测三问
- string、切片、map 的零值分别是什么?向零值 map 写入会发生什么?
- 写出 for 的三种形态,Go 用哪种形态替代 while?
divide返回(float64, error),为什么出错时要返回零值而不是 -1 这类魔数?
小结一句话
Go 用「零值保证 + 显式转换 + 单一 for + 多返回值 error」四件套,把最容易出事故的角落提前焊死:语法小,但约束严。
延伸
- Effective Go:控制结构
- Go 语言规范:常量
- 下一课:数组、切片与 map——Go 最常用的容器,切片底层结构是面试超高频题。