LEVEL 1
写得出、读得懂。掌握这一层就能写脚本和简单服务。
①
程序骨架:package / import / main
package main // 可执行程序必须是 main 包
import (
"fmt" // 标准库包,双引号
"time"
)
func main() { // 入口函数,无参数无返回值
fmt.Println("hello")
}
要点:package main + func main() 才是可执行入口;库包用别的包名。导入未使用的包会编译报错。
一行式导入可简写:import "fmt"。多包用括号分组导入,末尾逗号不要也行(Go 会自动补)。
②
变量与常量
var a int = 10 // 显式声明
var b = 20 // 类型推断
c := 30 // 短声明,只能在函数内
var x, y int = 1, 2 // 多变量
m, n := 3, 4
const Pi = 3.14159 // 常量,编译期确定
const ( // 常量组 + iota 计数器
A = iota // 0
B // 1
C // 2
)
:= 是「声明并赋值」,左侧必须至少有一个新变量;它不能在包级(函数外)使用,包级只能用 var / const。
③
基本类型
整型
int/int8/int32/int64、uint 家族;byte=uint8,rune=int32(Unicode 码点)。
浮点 / 复数
float32/float64(默认 64);complex64/128。
布尔 / 字符串
bool 只有 true/false;string 不可变、UTF-8、可按字节或 rune 遍历。
s := "你好Go"
fmt.Println(len(s)) // 8:按字节数(中文 3 字节)
for _, r := range s { // range 按 rune 迭代
fmt.Printf("%c ", r) // 你 好 G o
}
不同长度的整型之间不能隐式转换,必须显式:int64(a)。这与 C 不同,是 Go「显式即清晰」的体现。
④
复合类型:数组 / 切片 / Map / 结构体
// 数组:长度是类型的一部分,很少直接用
var arr [3]int = [3]int{1, 2, 3}
// 切片:动态数组,最常用。底层是 指针+len+cap
sl := []int{1, 2, 3}
sl = append(sl, 4) // 追加,可能重新分配
sl = sl[1:3] // 切片操作 [1,3)
sl2 := make([]int, 0, 10) // 预分配 cap=10
// Map:哈希表,零值不可用作,必须 make
mp := make(map[string]int)
mp["a"] = 1
v, ok := mp["b"] // ok=false 表示不存在,不会 panic
// 结构体
type User struct {
Name string
Age int
}
nil map 读得到(返回零值)但写入会 panic,务必 make。切片底层数组被大切片引用而释放不掉,是常见内存泄漏点。
⑤
控制流:if / for / switch / range
// if 可直接带初始化语句
if v, err := f(); err != nil {
return err
}
// Go 只有 for,没有 while
for i := 0; i < 3; i++ {} // 经典
for cond {} // 当 while
for {} // 死循环
for k, v := range sl {} // 迭代
// switch 默认自带 break,且条件可为任意类型
switch x.(type) {
case int:
fmt.Println("int")
default:
fmt.Println("other")
}
range 迭代 map/slice 时取的是「值拷贝」,若想改元素要取地址或用索引。
⑥
函数:多返回值 / 命名返回 / defer / 变参
// 多返回值:Go 的习惯是 (结果, error)
func div(a, b int) (int, error) {
if b == 0 { return 0, errors.New("div by zero") }
return a / b, nil
}
// 命名返回值 + defer 修改它(经典关闭资源范式)
func f() (err error) {
defer func() { err = recover() }() // 捕获 panic 写入 err
return doSomething() // 等价于 return err
}
// 变参
func sum(nums ...int) int { return 0 }
defer 是栈式(后进先出),参数在「注册时」取值,但函数体在 return 后执行。常用于关闭文件、解锁、记录耗时。
⑦
结构体与方法(值 vs 指针接收者)
type Counter struct{ n int }
// 值接收者:副本,不改原对象
func (c Counter) Get() int { return c.n }
// 指针接收者:改原对象,且避免大对象拷贝
func (c *Counter) Inc() { c.n++ }
func main() {
c := Counter{}
c.Inc() // Go 自动取地址 (&c).Inc()
fmt.Println(c.Get()) // 1
}
经验法则:如果方法需要修改接收者、或接收者是大对象,用指针;否则看类型整体一致性 —— 同一个类型的接收者风格要统一。
LEVEL 2
Go 的「鸭子类型」接口 + 轻量并发。这一层让你写出地道的 Go。
⑧
接口:隐式实现 / 空接口 / 类型断言
// 只要方法集匹配就算实现,无需 implements 声明
type Speaker interface { Speak() string }
type Dog struct{}
func (d Dog) Speak() string { return "woof" } // Dog 自动实现了 Speaker
func main() {
var s Speaker = Dog{} // 赋值时检查实现
fmt.Println(s.Speak())
var i interface{} = 42 // 空接口可装任何类型
if n, ok := i.(int); ok { // 类型断言
fmt.Println(n)
}
switch v := i.(type) { // 类型 switch
case int: fmt.Println("int", v)
default: fmt.Println("?")
}
}
Go 1.18+ 有 any(= 空接口)、comparable(可做 map key 的约束)。接口是「值 + 类型」二元组,含 nil 值的接口不等于 nil 接口 —— 经典坑。
⑨
错误处理:没有异常,只有 error
import ("errors"; "fmt")
// 构造错误
err := errors.New("boom")
err = fmt.Errorf("wrap: %w", err) // %w 可穿透 errors.Is/As
// 检查
if err != nil { return err }
// panic/recover 仅用于不可恢复的程序错误
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("fatal")
不要用 panic 当普通错误。仅在初始化失败、不可继续时用。错误传播时优先 fmt.Errorf("...: %w", err) 保留链路。
⑩
并发基础:goroutine + channel
// goroutine:在函数调用前加 go,开销仅几 KB
go doAsync()
// channel:goroutine 间通信(CSP 模型)
ch := make(chan int) // 无缓冲
buf := make(chan int, 10) // 有缓冲,cap=10
go func() { ch <- 1 }() // 发送
v := <-ch // 接收,会阻塞
close(ch) // 发送方关闭
v, ok := <-ch // ok=false 表示已关闭
// 单向 channel:限制方向,提升可读性
func producer(out chan<- int) {} // 只写
func consumer(in <-chan int) {} // 只读
「不要通过共享内存来通信,而要通过通信来共享内存。」—— 优先用 channel,而非 mutex + 共享变量。channel 关闭后不可再发送,否则 panic。
⑪
sync 同步原语
WaitGroup
等一组 goroutine 结束。Add/Done/Wait。Done 写在 defer 里。
Mutex / RWMutex
互斥 / 读写锁。读多写少用 RWMutex(RLock/RUnlock)。
Once
保证只执行一次。once.Do(init),常用于单例/懒加载。
Map
并发安全的 map,替代加锁的 map[string]any。
Pool
临时对象复用,缓解 GC 压力(见性能篇)。
atomic
无锁原子操作,比 mutex 轻。atomic.Int64 等。
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // 阻塞直到所有 Done
⑫
常用标准库速查
| 包 | 典型用途 | 高频函数 |
fmt | 格式化输入输出 | Println/Sprintf/Printf/Scan |
strings | 字符串处理 | Split/Trim/Contains/Replace/Builder |
strconv | 字符串 ↔ 数字 | Atoi/Itoa/ParseFloat/FormatInt |
time | 时间日期 | Now/Parse/Format/Add/After |
sort | 排序 | Slice/Ints/Search |
io / bufio | 流式读写 | Copy/Read/Writer/Scanner |
os | 系统交互 | Open/Args/Exit/Getenv |
encoding/json | JSON | Marshal/Unmarshal |
net/http | HTTP 客户端/服务端 | Get/Post/HandleFunc/ListenAndServe |
data, _ := json.Marshal(map[string]int{"a": 1})
var m map[string]int
json.Unmarshal(data, &m) // 注意传指针
LEVEL 3
高
高级篇 · 泛型 / Context / 模式 / 性能
写出可复用、可扩展、高性能的生产级 Go。
⑬
泛型(Go 1.18+)
// 类型参数 [T any],约束写在 interface 里
func Max[T comparable](a, b T) T {
if a > b { return a }
return b
}
// 自定义约束
type Number interface { int | int64 | float64 }
func Sum[T Number](ts []T) T { return ts[0] }
// 内置约束:any, comparable, ~int 等
type Cache[K comparable, V any] struct {
m map[K]V
}
标准库已提供 slices、maps、cmp 等泛型包(1.21+)。约束用 | 并集,~ 表示「底层类型」匹配。
⑭
Context:取消、超时、传值
// 超时控制:3 秒后自动取消
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() // 必须调用,释放资源
go func() {
select {
case <-time.After(5 * time.Second):
fmt.Println("done")
case <-ctx.Done(): // 被取消 / 超时
fmt.Println(ctx.Err()) // context deadline exceeded
}
}()
// 传值:跨 API 透传请求作用域数据(别乱用)
ctx = context.WithValue(ctx, "traceId", "abc")
Context 作为函数「第一个参数」贯穿调用链;不要存进 struct;WithValue 只放请求级元数据(traceId 等),不要放大对象。
⑮
并发模式
Worker Pool
固定 N 个 worker 从任务 channel 取活,限流防炸。
Fan-out / Fan-in
多 worker 分治,再汇总到一个 channel。
Pipeline
阶段间用 channel 串联,数据流式处理。
select 多路
select 同时等待多个 channel,配 default 防阻塞。
// worker pool 范式
jobs := make(chan int, 100)
for w := 0; w < 3; w++ {
go func() {
for j := range jobs { // 通道关闭后自动退出
process(j)
}
}()
}
⑯
性能与内存
pprof
import _ "net/http/pprof",访问 /debug/pprof 拿 CPU/heap 火焰图。
Benchmark
func BenchmarkX(b *testing.B) + go test -bench .。
sync.Pool
复用临时对象,减少分配与 GC:Get/Put。
atomic
计数器/标志用 atomic.Int64,免锁。
逃逸分析
go build -gcflags="-m" 看哪些变量逃到堆上。
预分配
make([]T, 0, n) 指定 cap,减少扩容拷贝。
var pool = sync.Pool{
New: func() any { return make([]byte, 1024) },
}
buf := pool.Get().([]byte)
defer pool.Put(buf) // 用完归还
Go 调度器是 GMP 模型(Goroutine/Machine/Processor),goroutine 在用户态调度,切换成本是线程的零头 —— 这是「并发是头等公民」的根基。GC 用三色标记法并发回收(详见本目录 golang-gc-tricolor.html)。
⑰
测试:表驱动 + Benchmark + Mock
// 文件 foo_test.go,函数名 TestXxx
func TestDiv(t *testing.T) {
tests := []struct{ a, b, want int }{
{6, 3, 2}, {0, 1, 0},
}
for _, tt := range tests {
if got := div(tt.a, tt.b); got != tt.want {
t.Errorf("%d/%d=%d want %d", tt.a, tt.b, got, tt.want)
}
}
}
// 运行:go test ./... 覆盖率:go test -cover
接口让 Mock 极其简单:测试时传入一个「假实现」即可,无需框架。Benchmark 用 b.ResetTimer() 排除准备耗时。
LEVEL 4 · 落地
把上面的语法落进真实场景:Web 服务、命令行、项目布局。
⑱
① Web 服务(net/http 原生)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello %s", r.URL.Query().Get("name"))
})
http.ListenAndServe(":8080", mux)
}
生产微服务常用 go-zero(本目录已有一整套教程:go-zero-basics / api / rpc / microservices)。需要更强路由用 gin、echo 等第三方框架。
⑱
② 命令行 CLI(flag)
var name = flag.String("name", "world", "who to greet")
var port = flag.Int("port", 8080, "listen port")
func main() {
flag.Parse()
fmt.Printf("hi %s on %d\n", *name, *port)
}
复杂 CLI 用 cobra;构建命令 go build -o app .,交叉编译 GOOS=linux GOARCH=amd64 go build。
⑱
③ 标准项目布局
myproject/
├── cmd/ // 各可执行程序入口
│ └── app/main.go
├── internal/ // 私有代码,外部不可 import
│ ├── service/
│ └── repo/
├── pkg/ // 可公开复用的库
├── api/ // api/rpc 定义(go-zero 用 .api/.proto)
├── configs/
├── go.mod
└── Makefile
internal/ 是 Go 编译器强制的「私有边界」:外部模块无法 import 它里面的任何东西。这是 Go 管理可见性的首选手段。
速查总图
一张表回顾分层能力
| 层级 | 你该掌握的 | 标志性语法 |
| 基础 | 写得出程序 | var:=、slice/map、for/range、struct |
| 中级 | 写地道 Go | interface{}、error、goroutine/channel、sync |
| 高级 | 写生产级 | 泛型、context、worker pool、pprof、GMP |
| 应用 | 落地交付 | net/http、flag、internal/pkg、go-zero |