Go 语言语法速查表 - Go 常用命令与并发编程大全

这套速查给用 Go 写接口服务、并发 worker 或运维工具的开发者,聚合最常见的语法骨架:变量声明、循环与分支、函数与 defer、结构体方法、接口,以及最容易写错的 goroutine/channel 并发。与单纯贴 API 文档不同,条目按"片断解决什么问题"给出可直接抄的写法。读完应能独立写出 goroutine + WaitGroup 的并发代码、用接口做依赖抽象,并按 Go 惯例用 err != nil 显式检查错误。

编程语言·共 44 条命令·最后更新 2026-07-21
gogolang编程并发

典型使用场景

面向用 Go 写后端服务与并发任务的工程师。Go 的四个必掌握点在本表都有对应分组:值类型与引用类型(map 取不存在键返回零值,这是 Go 最常见的逻辑 bug 来源)、显式错误处理(每个返回 error 的调用都要检查)、接口隐式实现(不需要 implements 关键字)、以及 goroutine 与 channel 的并发模型。本表按这四块加结构体方法组织,示例均可直接编译对照。

变量与常量 Variables & Constants 7

var name string = "value"
显式声明变量:先写关键字 var、类型、再赋值,适合类型需要明确指定的场景
name := "value"
短变量声明:由右侧表达式自动推断类型,只能在函数内部使用
const Pi = 3.14
声明编译期确定的常量,仅限字符串、布尔和数值等基本类型,不可在运行时改动
var (name string; age int)
var 块批量声明多个相关变量,便于成组集中管理
var arr [5]int
声明定长数组,长度是类型的一部分,arr[5] 与 arr[10] 是不同类型,不可互赋
slice := []int{1, 2, 3}
创建可变长切片,底层共用一段数组,append 扩容时可能重新分配底层数组
m := map[string]int{"a": 1}
创建键值对映射;读取不存在的键返回零值(此处为 0),判断存在要写成 v, ok := m[k]

控制流 Control Flow 8

if condition { } else { }
if 语句
if err := fn(); err != nil { }
if 带初始化语句
switch x { case 1: case 2: default: }
switch 语句
for i := 0; i < 10; i++ { }
for 循环
for condition { }
while 风格循环
for { }
无限循环
for i, v := range slice { }
遍历切片/数组
for k, v := range map { }
遍历 map

函数 Functions 6

func add(a, b int) int { return a + b }
普通函数
func swap(a, b int) (int, int)
多返回值
func sum(nums ...int) int
可变参数
defer fn()
延迟执行(函数退出时执行)
panic("error")
抛出恐慌(程序崩溃)
recover()
捕获恐慌(在 defer 中使用)

结构体与方法 Struct & Method 5

type User struct { Name string; Age int }
定义命名结构体类型;字段名首字母大写才在包外可见,首字母小写是私有字段
u := User{Name: "tom", Age: 20}
按字段名创建结构体实例,未写出的字段取对应类型的零值,比按位置初始化更稳
func (u User) String() string
值接收者方法:方法内操作的是结构的副本,直接改动字段不会影响原实例
func (u *User) SetName(n string)
指针接收者方法:可直接修改原实例字段,也用于避免拷贝大结构体
u := &User{}
创建结构体指针,配合指针接收者方法修改成员值

接口 Interface 5

type Reader interface { Read(p []byte) (int, error) }
定义接口
var r Reader = &File{}
接口实现(隐式,无需 implements)
type Empty interface{}
空接口(可存储任意类型)
v, ok := i.(string)
类型断言
switch v := i.(type) { }
类型选择

并发 Concurrency 8

go fn()
在函数前加 go 即在新 goroutine 并发执行;调用方不会等它完成,需用 channel 或 WaitGroup 主动同步
ch := make(chan int)
创建无缓冲 channel,发送方阻塞直到有对端接收,从而实现双向同步传递
ch := make(chan int, 10)
创建容量 10 的有缓冲 channel,容量未满时发送不阻塞,适合解耦生产/消费节奏
ch <- value
往 channel 发送值;无缓冲时可能阻塞,若 channel 已 close 会触发 panic
value := <-ch
从 channel 接收值;channel 关闭且排空后,接收方会立即得到零值
close(ch)
由发送方关闭 channel 通知接收方"不会再有了",用 for range 遍历时可据此退出循环
select { case <-ch1: case <-ch2: }
多路复用:同时等待多个 channel,命中的 case 才会执行,配合 for 可持续消费
var wg sync.WaitGroup
声明等待组,wg.Add/wg.Done/wg.Wait 配合,让主协程等待所有 goroutine 收尾而非直接退出

错误处理 Error Handling 5

if err != nil { return err }
标准错误检查
type MyError struct { msg string }
自定义错误类型
func (e *MyError) Error() string
实现 error 接口
errors.New("message")
创建错误
fmt.Errorf("failed: %w", err)
包装错误(Go 1.13+)

参数矩阵

参数作用示例
:=短变量声明,类型由右侧推断(仅函数内)name := "value"
const编译期常量,仅限基本类型const Pi = 3.14
slice := []int{}切片,可变长,append 可能换底层数组slice := []int{1, 2, 3}
v, ok := m[k]判断 map 中键是否存在v, ok := m["a"]
defer函数退出时执行,参数此时求值defer f.Close()
指针接收者可修改原实例字段,也避免拷贝大结构体func (u *User) SetName(n string)
值接收者操作副本,改动不影响原实例func (u User) String() string
v, ok := i.(string)类型断言(安全形式)switch v := i.(type) {}
interface{}空接口,可存任意类型var v interface{}
go fn()启动 goroutine,调用方不等待go process()
make(chan int, n)有缓冲 channel,容量未满不阻塞ch := make(chan int, 10)
close(ch)发送方关闭,接收方遍历可据此退出close(ch)
select同时等待多个 channelselect { case <-done: }
fmt.Errorf %w包装错误并支持 errors.Is/Asfmt.Errorf("failed: %w", err)

易错点与避坑指南

现象遍历 map 顺序每次都不一样,测试偶发失败。

原因Go 故意随机化 map 遍历顺序,不保证稳定。

处置需要有序输出时先把 key 收集到切片排序后再遍历。

现象写并发安全代码时出现数据竞争,go run -race 才报出来。

原因多个 goroutine 共享同一变量而无同步。

处置用 channel 传递数据,或用 sync.WaitGroup 等待收尾;提交前用 -race 跑一遍。

现象向已关闭的 channel 发送导致 panic。

原因channel 只能由发送方关闭一次,关闭后再发送会崩溃。

处置约定由发送方 close;多发送方场景用 sync.Once 保证只关一次。

现象goroutine 里的 panic 导致整个程序崩溃,无法被调用方感知。

原因没有在 goroutine 内部 recover。

处置在 goroutine 内的 defer 中调用 recover(),把 panic 转成日志或返回值。

现象map 读取不存在的键得到 0,被当成真实的零值继续计算。

原因Go 的 map 读取缺失键直接返回零值,不报错。

处置一律用 v, ok := m[k] 两值形式判断存在性,不要靠值是否为零判断。

现象用值接收者的方法修改字段,调用后原对象没变。

原因值接收者拿到的是副本,改动只作用于副本。

处置需要修改状态的方法改用指针接收者 (u *User);同一类型的接收者应保持一致。

现象defer 里的 os.Open 结果为 nil 时 Close 报错或掩盖真实错误。

原因Open 失败时返回 nil,defer 仍执行 Close。

处置先判断 err != nil 再返回,之后才注册 defer;或写成 if f != nil { defer f.Close() }。

现象循环里用 defer 导致文件直到函数结束才关闭。

原因defer 注册在函数退出时执行,不是循环迭代结束时。

处置把单次处理抽成独立函数,在该函数内 defer Close。

排障路径

  1. 1查数据竞争

    go run -race main.go

    race detector 能定位并发读写同一变量的位置,提交前必跑。

  2. 2确认错误被正确包装

    fmt.Errorf("failed: %w", err)

    用 %w 包装,配合 errors.Is / errors.As 才能沿调用链判断。

  3. 3判断 map 中键是否存在

    v, ok := m[k]

    不要用 v == 0 判断缺失,因为零值可能是合法数据。

  4. 4遍历 map 前先排序保证输出稳定

    for _, k := range keys { v := m[k] }

    Go 的 map 顺序随机,测试与日志输出都需要显式排序。

  5. 5goroutine 内兜住 panic

    go func() { defer func() { recover() }(); work() }()

    goroutine 内的 panic 不会被调用方 recover,必须就地处理。

命令示例

标准错误链:包装 + 判定 + recover 兜底

func load(id string) error { if id == "" { return fmt.Errorf("load: %w", errors.New("empty id")) } return nil } func handle() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }() return load("") }

fmt.Errorf 的 %w 把底层错误包进链里,上层可以用 errors.Is/As 逐层判定;defer 里必须给返回值命名,才能在 recover 后替换要返回的 error。

并发写库:WaitGroup 等待 + 错误汇总

var wg sync.WaitGroup errCh := make(chan error, 3) for _, id := range ids { wg.Add(1) go func(id string) { defer wg.Done() if err := save(id); err != nil { errCh <- fmt.Errorf("save %s: %w", id, err) } }(id) } wg.Wait() close(errCh) for err := range errCh { return err }

Go 1.22 之前的循环变量是同一个,goroutine 里直接闭包捕获 id 会全部拿到最后一个值,必须把 id 作为参数传进去。errCh 必须有缓冲,否则全部 goroutine 都在等它、wg.Wait 永远不返回。

channel 关闭 + select 超时/多路复用

go func() { defer close(ch) for _, v := range items { ch <- v } }() for { select { case v, ok := <-ch: if !ok { return } use(v) case <-time.After(2 * time.Second): return errors.New("consume timeout") } }

发送方负责 close,接收方用 v, ok := <-ch 判断是否排空;select 的多个 case 同时就绪时随机选一个,所以关闭信号要和数据竞争同一条 channel。

结构体 + 指针接收者 + 接口实现与类型断言

type User struct { Name string age int } func (u *User) SetAge(a int) { u.age = a } type Reader interface { Read(p []byte) (int, error) } var r Reader = &User{} switch v := r.(type) { case *User: v.SetAge(18) }

值接收者改字段只改副本,要真正写入必须用指针接收者(并且通过指针调用)。接口实现是隐式的,任何满足方法集的指针类型都直接赋给 Reader。

常见坑与注意事项

  • 向 nil map 写入直接 panic(assignment to entry in nil map):var m map[string]int 只可用于读取,声明处就得 make 或初始化字面量,否则运行到那一行才崩。
  • defer 绑定的是函数不是循环:写在 for 循环体里的 defer 要等整个函数返回才执行,循环一万次就会攒一万个延迟调用(文件句柄/锁耗尽),需要即时释放应封装成小函数调用。
  • goroutine 泄漏最难排查:向无缓冲 channel 发送后再无人接收,或 select 少了一个退出分支,goroutine 会永久阻塞且不报错,表现为内存单调上涨。
  • 对已 close 的 channel 发送会 panic,重复 close 也会 panic;关闭责任必须唯一地交给发送方,否则典型报错是 send on closed channel。
  • 把带类型的 nil 指针赋给接口后,接口本身不等于 nil:err != nil 仍为 true,这个陷阱会让错误被误判为成功,且没有任何编译期提示。
  • append 与切片共享底层数组:sub := s[1:3] 之后对 sub 的 append 可能改到原数组尾部,超过容量时又悄悄换新数组,同一份代码加个元素就出现难查的偶发数据错乱。

提示

  • Go 的错误处理是显式的,每个可能返回 error 的函数都要检查 err != nil。
  • goroutine 非常轻量(几 KB),可以轻松创建数十万个。
  • channel 是 goroutine 之间通信的主要方式,遵循 "不要通过共享内存通信,而要通过通信共享内存"。

常见问题

:= 和 var 有什么区别?

:= 是短变量声明,由右侧表达式自动推断类型,只能在函数内部使用;var 需要显式写类型,且可用于包级作用域。实际写业务代码优先用 :=,因为类型由编译器推导不易出错;var 更适合声明零值或类型无法从右侧推断的情况。另外 := 至少要有一个左侧变量是新的,否则就是编译错误。

值接收者和指针接收者的方法有什么区别?

值接收者(func (u User) String())操作的是结构体的副本,方法内改动字段不影响原实例;指针接收者(func (u *User) SetName(n))直接修改原实例,也能避免大结构体的拷贝开销。经验法则是:涉及修改就用指针接收者,只做格式化输出的 String 这类纯读方法用值接收者。同一个类型不要混用两种,否则接口实现会出现意外。

无缓冲 channel 和有缓冲 channel 有什么不同?

无缓冲 channel 的发送方会一直阻塞,直到有接收方取走,天然完成收发双方同步;make(chan int, 10) 创建的带缓冲 channel 在容量未满时发送不阻塞,能解耦生产与消费节奏。前者适合明确的一对一交接,后者适合生产者快于消费者的流水线。往已关闭的 channel 发送会 panic,接收方则会在排空后得到零值。

defer 的执行顺序是怎样的?

defer 把它挂到当前函数上,在函数返回前按后进先出(LIFO)的顺序执行,与代码书写顺序相反。所以在一段函数里先注册的 defer 会最后运行,常用于打开文件后紧跟注册的关闭操作。反过来,如果你希望清理最先执行,就要把它注册在其他 defer 之前。

error 应该怎么处理?

Go 的惯例是错误作为返回值而非抛出:if err := fn(); err != nil { return err }。需要保留原始错误链时用 fmt.Errorf("failed: %w", err) 包装,调用方就能用 errors.Is 和 errors.As 判断错误类型或取出结构化信息。只有真正无法恢复的编程错误才用 panic。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们