本工具使用的开源库

本工具代码中捆绑了 1 个开源库。

Go 速查 — 简明参考

Go 1.22 语法、类型、goroutine 与最常用标准库速查手册,覆盖约 80% 日常场景。

Go

Go Go 1.22

Go (gc toolchain) · 编译型 · 并发 · 命令式 · 静态 · 强类型 · 结构化(接口)

学习路径

先学会 go run/build 与 go.mod 模块 → 掌握变量、类型与流程控制 → 深入函数、指针与 struct → 用 slice/map 组织数据 → 理解接口与错误处理 → 用 goroutine/channel 写并发 → 再按需学 http、context、时间与构建测试。FAQ 节适合回头避坑。

1.Hello World 与构建环境

运行 Go 程序、go.mod 模块与工具链。

最小程序

每个程序以 package main + func main 为入口。fmt 输出。

1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
// 运行:
// go run hello.go
// package main 生成可执行程序
// 其他 package 是库
// main 函数无返回值

运行与构建

go run 编译并运行、go build 生成可执行文件、go install 安装到 bin。

1
2
3
4
5
6
7
8
9
10
11
12
// 直接运行:
// go run main.go
// 运行整个包:
// go run .
// 构建可执行文件:
// go build -o hello .
// 安装到 $GOBIN:
// go install .
// 检查编译:
// go vet ./...
// 输出二进制名默认模块名
// Windows 生成 .exe

go.mod 模块

go.mod 声明模块路径与依赖。go mod init 初始化、go mod tidy 整理依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化模块:
// go mod init example.com/hello
// go.mod 内容:
// module example.com/hello
// go 1.22
// 添加依赖:
// go get github.com/gin-gonic/gin
// 整理依赖:
// go mod tidy
// 更新依赖:
// go get -u ./...
// 依赖缓存:
// 所有模块在 GOPATH/pkg/mod
// 校验:go mod verify

包与导入

import 导入标准库与第三方。同目录文件同包。导入未使用报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
"fmt"
"os"
"strings"
)
// 导入第三方:
// import "github.com/gin-gonic/gin"
// 别名:
// import f "fmt"
// 空导入(触发 init):
// import _ "net/http/pprof"
// 导入未使用编译失败:
// 用 _ 抑制
// 包名与导入路径最后一段一致

格式化输出

fmt.Println/Printf/Print。Printf 动词 %s %d %v。Sprintf 返回字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
fmt.Println("Hello") // 自动换行
fmt.Printf("Age: %d\n", 30)
fmt.Printf("%s is %d\n", "Nick", 30)
// 常用动词:
// %v 默认值 %#v 结构 %T 类型
// %d 整数 %f 浮点 %.2f 两位
// %s 字符串 %q 带引号
// %t 布尔 %x 十六进制
// 返回字符串:
s := fmt.Sprintf("sum: %d", 1+1)
}

参数与输入

os.Args 命令行参数、flag 解析、fmt.Scan 读取输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
"fmt"
"os"
)
func main() {
// os.Args[0] 是程序路径
args := os.Args[1:]
fmt.Println("参数:", args)
// 读取输入:
var name string
fmt.Scanln(&name) // 读一行
// 带提示:
fmt.Print("输入: ")
var n int
fmt.Scan(&n)
}

多文件程序

同一目录多 .go 文件属于同包,可直接互相调用。文件共享包作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// utils.go
package main
func helper() string {
return "hi"
}
// main.go
package main
import "fmt"
func main() {
fmt.Println(helper()) // 直接调用
}
// 运行:
// go run .
// 同包标识符互通
// 函数名小写包内私有
// 大写出包(导出)

工具链

go version 版本、go env 环境、go doc 文档、go fmt 格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 版本:
// go version
// 环境变量:
// go env GOPATH GOROOT
// go env GOPROXY # 代理
// 文档:
// go doc fmt.Println
// go doc strings.Builder
// 格式化:
// gofmt -w .
// 依赖图:
// go list -m all
// 更新工具链:
// go get golang.org/dl/go1.22.0
// 帮助:go help <命令>

2.变量与常量

变量声明、短声明、常量、零值与类型转换。

变量声明

var 声明变量,类型在变量名后。声明时必须初始化或给零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name string
var age int
var ok bool
// 带初始化:
var count int = 5
var title = "Go" // 类型推断
// 类型在名后:
var score float64
// 批量声明:
var (
a int
b string
)
// 未初始化的变量有零值
// 声明未使用编译失败

短变量声明

:= 声明并初始化,类型推断。函数内使用,至少一个新变量。

1
2
3
4
5
6
7
8
9
10
11
name := "Go" // 推断 string
count := 42
flag := true
// 已有变量 + 新变量:
name, age := "Nick", 30
age = 31 // 已存在用 =
// 重新声明:
// := 至少一个左边是新变量
// 函数外不能使用 :=
// 作用域内重复声明报错
// x, y := 1, 2

常量

const 常量编译期确定。iota 自增枚举。常量可任意精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Pi = 3.14
const (
StatusOK = 200
StatusNotFound = 404
)
// iota 自增:
const (
A = iota // 0
B // 1
C // 2
)
// 类型常量:
const MaxSize int = 100
// 无类型常量可做任意类型:
const N = 10
var f float64 = N // 合法
// 常量不能修改

零值

未初始化的变量有零值:数字 0、字符串空串、bool false、指针 nil。零值可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var n int // 0
var s string // ""
var b bool // false
var p *int // nil
var arr [3]int // [0 0 0]
var m map[string]int // nil map
// 零值可直接用:
var buf bytes.Buffer
t := buf.String() // ""
// nil slice 可 append:
var nums []int
nums = append(nums, 1)
// nil map 不能写:
// m["a"] = 1 // panic
// 结构体零值:var r bytes.Reader

多重赋值

一行赋值多个变量,支持交换。函数多返回值解包。

1
2
3
4
5
6
7
8
9
10
11
a, b := 1, 2
a, b = b, a // 交换
// 忽略某个值:
x, _ := getPair()
// 多返回值解包:
value, err := risky()
// 部分更新:
map1["a"], n := "x", 1
// 同时初始化:
// 但 := 要求至少一个新变量
// 函数返回值解包数量必须匹配

作用域

块作用域:函数内变量仅在块内可见。遮蔽 shadowing。包级变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
var global = 10 // 包级
func f() {
x := 1
if y := x + 1; y > 0 {
z := y * 2 // if 块内
_ = z
}
// y、z 在这里不可见
}
// 遮蔽:
func g() {
global := 99 // 遮蔽包级 global
_ = global
}
// 短声明遮蔽外层变量
// 循环变量块作用域

类型转换

T(x) 显式转换。Go 不隐式转换数字类型。转换可能溢出/截断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var i int = 42
f := float64(i) // int -> float64
// 浮点转整数截断:
n := int(3.99) // 3
// 字符串转数字:
// 用 strconv:
s := strconv.Itoa(42) // "42"
n2, _ := strconv.Atoi("42")
// 字节转字符串:
s2 := string([]byte{'h', 'i'})
// 注意:string(65) 是 "A" 不是 "65"
// rune 转换:
r := rune(20013)
// 溢出截断:
// var b byte = 300 编译错误

命名约定

首字母大写导出(跨包可见),小写包内私有。驼峰命名。短变量名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package shapes
// 导出(首字母大写):
export const Area = 100
func Calculate() float64 // 导出
// 私有(小写):
func helper() int // 包内
var internalCount = 0
// 驼峰:
// maxRetries / getUserById
// 首字母缩写保持大写:
// theURL / HTTPClient
// 建议:
// 短名用于局部变量(i、n)
// 包名全小写单数

3.数据类型

内置类型、struct、数组、接口与泛型。

基础类型

bool、string、整数、浮点、复数、byte/rune 别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var b bool
var s string
// 整数:
var i int // 平台相关
var i8 int8; var u uint
// 浮点:
var f32 float32; var f64 float64
// 复数:
var c complex128
// 别名:
var by byte = 'a' // uint8
var r rune = '中' // int32
// 无符号:
uint8 uint16 uint32 uint64
// 平台:int/uint 64 位
// 长度:
// byte 1 字节 rune 4 字节

数值运算

算术运算、位运算、math 包。整数除法截断。溢出检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a, b := 7, 2
a / b // 3(整数除法截断)
a % b // 1
// 浮点:
// math.Sqrt(16) math.Pow(2, 3)
// math.Max/Min math.Abs
// 位运算:
// & | ^ << >>
// 溢出:
// var x int8 = 127; x++ // -128
// 检测溢出(1.18+):
// a, ok := bits.Add(a, b, 0)
// 随机数:math/rand
// 常量运算任意精度

struct

struct 组合字段。命名字段 + 位置初始化。字段标签 tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type User struct {
Name string
Age int
Tags []string
}
// 初始化:
u := User{Name: "Nick", Age: 30}
u2 := User{"Anna", 25, nil} // 位置(不推荐)
// 访问:
u.Name = "New"
// 零值:
var u3 User // 全零值
// 字段标签:
// type S struct {
// ID int `json:"id"`
// }
// 指针使用自动解引用:
p := &u
p.Name = "X" // (*p).Name

数组

数组定长、值类型(复制)。很少直接用,多数用 slice。

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr [3]int // [0 0 0]
arr[0] = 10
// 初始化:
arr2 := [3]int{1, 2, 3}
arr3 := [...]int{1, 2, 3} // 推断长度
// 长度:len(arr)
// 值类型:
b := arr2
b[0] = 99 // arr2 不变
// 比较:
// [3]int{1,2,3} == [3]int{1,2,3}
// 数组做参数是复制
// 传引用用切片或指针

type 定义

type 定义新类型或别名。新类型与原类型不同(需转换)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Celsius float64
type ID string
// 新类型:
var c Celsius = 25.0
// 不同方法集,需转换:
var f float64 = float64(c)
// 方法:
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", c)
}
// 类型别名(1.9+):
type MyInt = int // 完全等同
// 定义命名类型后可加方法
// 基于切片/map 定义:
type Stack []int

泛型

泛型函数用 [T any] 类型参数。约束 interface 限定类型集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import "fmt"
// 泛型函数:
func Min[T int | float64](a, b T) T {
if a < b { return a }
return b
}
// 约束:
// any 表示任意类型
// comparable 可比较
// 泛型类型:
type Pair[K comparable, V any] struct {
Key K
Value V
}
// 使用:
p := Pair[string, int]{Key: "a", Value: 1}
fmt.Println(Min(1, 2), Min(1.5, 2.5))
// 约束接口:
// type Number interface { ~int | ~float64 }

interface 类型

interface 定义方法集。空接口 interface{}(any)表示任意类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Shape interface {
Area() float64
}
// 实现:
type Circle struct{ R float64 }
func (c Circle) Area() float64 {
return 3.14 * c.R * c.R
}
// 使用:
func PrintArea(s Shape) {
fmt.Println(s.Area())
}
// 空接口(1.18+ 用 any):
func PrintAny(v any) {
fmt.Println(v)
}
// 断言:
if c, ok := s.(Circle); ok {
fmt.Println(c.R)
}
// 接口值 = 动态类型 + 值

comparable 约束

comparable 内置约束:可用 == 比较的类型。map 键、泛型比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// comparable 约束:
func Contains[T comparable](items []T, target T) bool {
for _, v := range items {
if v == target {
return true
}
}
return false
}
// 可比较类型:
// 数字、字符串、bool、指针、
// struct(字段全可比较)、数组
// 不可比较:
// slice、map、func、含它们的 struct
// 使用:
// Contains([]string{"a"}, "a")
// map 键必须可比较
// 泛型约束可用 comparable 替代 == 报错

4.指针

指针与地址操作:取地址、解引用、new/make、栈与堆。

取地址 &

& 取变量地址返回指针。指针保存变量的内存位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
n := 42
p := &n // p 是指向 n 的指针
fmt.Println(p) // 地址
fmt.Println(*p) // 42
// 指针类型:*int
// 指向变量可被修改:
*p = 99
fmt.Println(n) // 99
// & 对字段/数组元素:
// &arr[0]
// 指针不能取字面量地址:
// &42 错误
// 结构体取地址:&User{...}

解引用 *

*p 访问指针指向的值。结构体指针自动解引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Point struct{ X, Y int }
pt := Point{1, 2}
p := &pt
// 自动解引用:
p.X = 10 // (*p).X
(*p).Y = 20
// 需要显式解引用的场景:
q := new(int)
*q = 7
// 链式:
// (*ptr).field.method()
// 空指针解引用 panic:
// var p *int; *p = 1 // 崩溃
// 多层指针 * *int

nil 指针

nil 指针是零值。解引用 nil 指针 panic。使用前判空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var p *int // nil
// 判断:
if p != nil {
fmt.Println(*p)
} else {
fmt.Println("p 是 nil")
}
// 解引用 nil 崩溃:
// *p = 1 // panic: nil pointer
// 常见模式:
func safeDeref(p *int) int {
if p == nil {
return 0
}
return *p
}
// 返回 nil 表示无值:
// 函数返回 (T, bool) 或 *T

指针参数

指针参数可修改调用方变量。值参数是副本。大结构体用指针省复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 值参数(副本):
func setZeroValue(n int) {
n = 0 // 只改副本
}
// 指针参数(修改调用方):
func setZeroPtr(n *int) {
*n = 0
}
// 使用:
x := 100
setZeroPtr(&x)
fmt.Println(x) // 0
// 结构体指针:
func Scale(p *Point, f int) {
p.X *= f
p.Y *= f
}
// 大结构体传指针省复制
// 修改需持久化用指针

new 与 make

new 分配值类型返回指针。make 初始化 slice/map/channel。二者用途不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// new:返回指针,零值
p := new(int)
// *p == 0
pp := new(User) // 零值结构体指针
// make:初始化引用类型
nums := make([]int, 0, 10) // slice
m := make(map[string]int) // map
ch := make(chan int) // channel
// 区别:
// new(T) 返回 *T,零值
// make 仅 slice/map/channel
// 返回初始化后的非 nil 值
// map 必须 make 才能写
// slice 可直接声明 nil append

指针惯用法

指针用于共享可变状态。返回指针或值看语义。方法接收者选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 返回指针:
func NewUser(name string) *User {
return &User{Name: name}
}
// 指针接收者方法:
func (u *User) SetName(name string) {
u.Name = name // 修改原对象
}
// 值接收者:
func (u User) FullName() string {
return u.Name
}
// 何时用指针:
// 1. 修改接收者
// 2. 大结构体避免复制
// 3. 语义上希望共享
// 何时用值:
// 小结构体、不可变语义
// 指针一致性:同类型方法统一

栈与堆

编译器决定变量在栈或堆。逃逸分析。取地址可能逃逸到堆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 局部变量默认在栈:
func local() int {
x := 42
return x // 返回值,x 可栈分配
}
// 取地址逃逸到堆:
func escape() *int {
x := 42
return &x // x 逃逸到堆
}
// 查看逃逸:
// go build -gcflags='-m' .
// 逃逸原因:
// 1. 返回局部变量地址
// 2. 地址存入全局/堆对象
// 3. 接口装箱、闭包捕获
// 逃逸增加 GC 压力,但不必过度优化

指针安全

Go 指针无算术运算(禁止 C 风格 +1)。GC 管理生命周期,无悬垂指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无指针算术:
// p = p + 1 错误
// 不存在的操作
// 安全访问(编译器保证):
// 不会越界/悬垂
// 指针与 slice:
nums := []int{1, 2, 3}
p := &nums[0]
// 指针指向数组元素
// unsafe 包绕过(慎用):
// import "unsafe"
// 转换需手动保证安全
// 空指针检查:解引用前判 nil
// 内存安全由 GC + 编译期保证

5.流程控制

if、for、switch、defer 与循环控制。

if / else

if 条件无括号。可在条件前加初始化语句。条件必须是布尔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
score := 85
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else {
fmt.Println("C")
}
// 初始化语句:
if err := doSomething(); err != nil {
fmt.Println(err)
}
// err 仅在 if/else 块内可见
// 条件必须是布尔(不能用非零判断)
// 赋值非比较:
// if ok = true 错误,需 if ok

for 循环

Go 只有 for。三种形式:完整、while 式、无限循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 完整形式:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while 形式:
n := 0
for n < 5 {
n++
}
// 无限循环:
for {
break // 或 return
}
// 多变量:
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
}
// 省略初始化/条件:
// for ; n < 3; n++

range 遍历

range 遍历数组/slice/map/字符串/channel。1.22+ 循环变量每次独立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nums := []int{1, 2, 3}
for i, v := range nums {
fmt.Println(i, v)
}
// 只要值:
for _, v := range nums
// 只要索引:
for i := range nums
// 遍历 map:
for k, v := range m { }
// 遍历字符串(按 rune):
for i, r := range "你好" {
fmt.Println(i, r) // 每 rune
}
// 遍历 channel:
for v := range ch { }
// 1.22+ 循环变量每次迭代独立副本
// 旧版本 goroutine 闭包需注意

switch

switch 无 break 穿透。case 多值、无表达式(if-else 链)、type switch。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
day := "mon"
switch day {
case "mon", "tue":
fmt.Println("工作日")
case "sat", "sun":
fmt.Println("周末")
default:
fmt.Println("其他")
}
// 无表达式(布尔条件):
score := 75
switch {
case score >= 90:
fmt.Println("A")
default:
fmt.Println("其他")
}
// 类型判断:
var v any = "str"
switch t := v.(type) {
case string:
fmt.Println("string", t)
case int:
fmt.Println("int", t)
}

defer

defer 延迟执行到函数返回。后进先出。清理资源常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func readFile() error {
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close() // 函数结束时关闭
// 处理...
return nil
}
// 多个 defer 后进先出:
func example() {
defer fmt.Println("first")
defer fmt.Println("second") // 先打印
}
// defer 参数在声明时求值:
func count() {
i := 0
defer fmt.Println(i) // 0
i = 10
}
// 用途:
// 关闭文件/连接、解锁、恢复 panic

break 与 continue

break 退出循环,continue 跳本轮。可带标签控制嵌套循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
if i > 7 {
break
}
fmt.Println(i) // 1 3 5 7
}
// 标签控制外层:
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer // 跳外层下一轮
}
fmt.Println(i, j)
}
}
// break outer 退出外层循环
// 标签与循环同行或前一行

带初始化 switch

switch 前可加初始化语句。条件与初始化合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
switch n := rand.IntN(3); n {
case 0:
fmt.Println("零")
case 1:
fmt.Println("一")
default:
fmt.Println("其他")
}
// 初始化变量仅 switch 内可见
// 等价写法:
// n := rand.IntN(3)
// switch n { ... }
// 用途:错误处理链:
// switch err := f(); err {
// case nil: ...
// default: log(err)
// }
// case 不匹配自动到 default

goto 与标签

goto 跳转到标签。Go 支持但少用。用于深度嵌套错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func process(items []int) error {
for i, v := range items {
if v < 0 {
goto failure
}
_ = i
}
return nil
failure:
return errors.New("发现负数")
}
// 注意事项:
// 标签必须在使用后声明
// 不能跳入/跳过变量声明
// goto 不能跳入另一个块
// 多数场景 defer/错误返回更清晰
// 谨慎使用,可读性优先

6.函数

函数定义、多返回值、闭包、方法、函数值。

函数定义

func 定义函数。参数带类型,返回类型在最后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
// 参数类型合并:
func sub(a, b int) int {
return a - b
}
// 无返回值:
func log(msg string) {
fmt.Println(msg)
}
// 调用:
func main() {
sum := add(1, 2)
fmt.Println(sum)
}
// 函数参数是值传递(副本)
// 传引用用指针/slice/map

多返回值

Go 函数可返回多个值。惯例返回 (值, error)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func div(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数为零")
}
return a / b, nil
}
// 使用:
result, err := div(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println(result)
}
// 忽略部分:
q, _ := div(10, 2)
// 多个有效值:
func minMax(nums []int) (int, int) {
return nums[0], nums[len(nums)-1]
}
// 约定:
// 最后一个返回值是 error
// 出错时其他返回零值

命名返回值

返回值可命名。函数内直接赋值,return 裸返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 裸返回 x, y
}
// 使用场景:
func stats(nums []int) (sum, avg float64) {
sum = 0
for _, v := range nums {
sum += float64(v)
}
avg = sum / float64(len(nums))
return
}
// 注意:
// 裸返回可读性有争议
// defer 可修改命名返回值
// 命名返回值有零值
// 复杂返回用命名更清晰

可变参数

...T 收集任意数量参数为 slice。展开调用 slice...。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 调用:
sum(1, 2, 3) // 6
sum() // 0
// 展开 slice:
nums := []int{1, 2}
sum(nums...) // 展开为多个参数
// 混合:
// func greet(prefix string, names ...string)
// 可变参数必须是最后一个
// 类型是 []T(同 slice)

闭包

闭包捕获外层变量。返回函数记住状态。1.22+ 循环变量独立捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func makeCounter() func() int {
count := 0
return func() int {
count++ // 捕获 count
return count
}
}
// 使用:
counter := makeCounter()
counter() // 1
counter() // 2
// 闭包捕获的是变量本身:
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
// 各自独立状态:
c1, c2 := makeCounter(), makeCounter()
// 1.22+ 循环变量每次迭代独立

方法

方法在类型上定义,接收者出现在 func 与函数名之间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Rectangle struct {
Width, Height float64
}
// 值接收者:
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 指针接收者:
func (r *Rectangle) Scale(f float64) {
r.Width *= f
r.Height *= f
}
// 使用:
rect := Rectangle{2, 3}
fmt.Println(rect.Area())
rect.Scale(2)
// 方法与函数区分:
// 方法绑定到类型
// 方法不能定义在非本包类型上
// 指针接收者可修改原对象

函数值

函数是一等公民:可赋值、传参、返回。函数类型签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 函数类型:
type Operation func(a, b int) int
func add(a, b int) int { return a + b }
func apply(op Operation, x, y int) int {
return op(x, y)
}
// 使用:
result := apply(add, 3, 4) // 7
// 匿名函数:
f := func(a, b int) int { return a - b }
// 作为参数(回调):
func filter(nums []int, pred func(int) bool) []int {
out := make([]int, 0)
for _, n := range nums {
if pred(n) {
out = append(out, n)
}
}
return out
}
// 常用:sort.Slice 传比较函数

init 函数

init 在包导入时自动执行。每个文件可有多个 init。用于初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"
var config map[string]string
func init() {
config = make(map[string]string)
config["env"] = "dev"
fmt.Println("初始化完成")
}
// 特点:
// 在 main 前执行
// 每个文件可多个 init
// 多个 init 按文件/顺序执行
// 依赖包的 init 先执行
// 用途:
// 注册驱动、初始化配置
// 无法用 var 初始化时
// 避免依赖 init 顺序

7.字符串

string 不可变、rune/字节、strings 包与格式化。

字符串基础

字符串是不可变字节序列。双引号字面量、反引号原始串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s := "hello"
// 反引号原始字符串:
raw := `多行
文本`
// 长度(字节数):
len("hello") // 5
len("你好") // 6(UTF-8 每字 3 字节)
// 索引(字节):
s[0] // 'h' (byte)
// 拼接:
s2 := "a" + "b"
// 不可变:
// s[0] = 'x' 错误
// 复制:
// 字符串赋值是复制引用(不可变安全)
// 字符串可比较:== < >

rune 与字符

rune 是 Unicode 码点(int32)。range 按 rune 遍历。utf8 解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s := "你好, Go"
// range 按 rune:
for _, r := range s {
fmt.Printf("%c ", r)
}
// 你好 , 空格 G o
// 转 rune 切片:
rs := []rune(s)
fmt.Println(len(rs)) // rune 数
// 字节切片:
bs := []byte(s)
// rune 字面量:
ch := '中' // rune
// 解码:
import "unicode/utf8"
// utf8.RuneCountInString(s)
// utf8.DecodeRuneInString
// 索引是字节位置,别按 len 截断中文

strings.Builder

Builder 高效拼接字符串。避免 + 循环拼接产生垃圾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "strings"
var b strings.Builder
b.WriteString("hello")
b.WriteString(", ")
b.WriteString("world")
result := b.String() // "hello, world"
// 预分配:
b.Grow(100) // 预先容量
// 写字节/字符:
b.WriteByte('!')
b.WriteRune('中')
// 返回长度:
b.Len()
// 用途:
// 循环拼接、生成 HTML/CSV
// 比 + 拼接高效(避免多次分配)
// 比 bytes.Buffer 语义更贴合字符串

strings 包

Contains/Split/Join/Trim/Replace 等字符串操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "strings"
s := " Hello World "
strings.TrimSpace(s)
strings.ToUpper(s)
strings.ToLower(s)
strings.Contains(s, "World") // true
strings.HasPrefix(s, " ")
strings.HasSuffix(s, " ")
strings.Replace(s, "World", "Go", -1)
strings.ReplaceAll(s, " ", "-")
// 分割:
parts := strings.Split("a,b,c", ",")
strings.Join([]string{"a", "b"}, "-")
// 子串:
strings.Index(s, "World")
strings.LastIndex(s, "o")
// 计数:
strings.Count(s, "o")
// 比较:
strings.Compare("a", "b")
strings.EqualFold("Go", "go") // 忽略大小写

fmt 格式化

Printf/Sprintf 动词。宽度、精度、填充。Fprintf 写 io.Writer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
name, age := "Nick", 30
fmt.Printf("%s 今年 %d 岁\n", name, age)
// 动词:
// %v 默认 %+v 带字段名 %#v Go 语法
// %d 十进制 %x 十六进制
// %f 浮点 %.2f 两位
// %s 字符串 %q 带引号
// %t 布尔 %T 类型
// %5d 宽度 %-5d 左对齐
// %05d 补零
// %10.2f 宽度 10 两位
// 返回字符串:
s := fmt.Sprintf("%s-%d", name, age)
// 写 io.Writer:
fmt.Fprintf(buf, "%s", name)

strconv 转换

字符串与数字互转、字符串与布尔互转。转义处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "strconv"
// 数字转字符串:
s := strconv.Itoa(42) // "42"
// 字符串转数字:
n, err := strconv.Atoi("42")
// 解析浮点:
f, err := strconv.ParseFloat("3.14", 64)
// 指定进制:
n64, _ := strconv.ParseInt("101", 2, 64)
// 格式化:
strconv.FormatInt(255, 16) // "ff"
strconv.FormatFloat(3.14, 'f', 2, 64)
// 布尔:
b, _ := strconv.ParseBool("true")
strconv.FormatBool(true)
// 引号:
strconv.Quote("hi") // "\"hi\""
// 进制格式化:
fmt.Sprintf("%x", 255) // "ff"

编码处理

base64、hex、URL 编码。bytes 与字符串互转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
"encoding/base64"
"encoding/hex"
"net/url"
)
// base64:
b64 := base64.StdEncoding.EncodeToString([]byte("hi"))
decoded, _ := base64.StdEncoding.DecodeString(b64)
// hex:
h := hex.EncodeToString([]byte("\x00\xff"))
// URL 编码:
url.QueryEscape("a b&c") // "a+b%26c"
// 字节与字符串:
s := string([]byte{104, 105})
bs := []byte(s)
// 十六进制输出:
fmt.Printf("%x\n", "hi")
// 编码解码错误要处理 err

字符遍历

按 rune 遍历字符串处理中文。range 与字节索引的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s := "你好 Go"
// range 按 rune 遍历:
for _, r := range s {
fmt.Printf("%c", r)
}
// 需要索引:
for i, r := range s {
fmt.Println(i, r) // 字节偏移 + 码点
}
// 字节索引(纯 ASCII):
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
// 转为 []rune:
rs := []rune(s)
// 取第 n 个字符:
fmt.Println(string(rs[2]))
// 中文务必按 rune 处理

8.集合

slice、map、数组与排序。

切片 slice

slice 动态数组,引用底层数组。nil slice 可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nums := []int{1, 2, 3} // 字面量
var s []int // nil slice
// 追加:
nums = append(nums, 4)
// 空 slice:
empty := []int{}
// 长度与容量:
len(nums); cap(nums)
// 切片:
sub := nums[1:3] // 视图共享底层
// 引用底层数组:
// 修改 sub 影响 nums
// append 超容量会分配新数组
// make 指定容量:
make([]int, 5, 10)

切片操作

append 追加、copy 复制、删除元素、插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
nums := []int{1, 2, 3, 4, 5}
// 追加多个:
nums = append(nums, 6, 7)
// 拼接切片:
nums = append(nums, []int{8, 9}...)
// 删除索引 2:
nums = append(nums[:2], nums[3:]...)
// 复制:
dst := make([]int, len(nums))
copy(dst, nums)
// 前插:
nums = append([]int{0}, nums...)
// 清空:
nums = nums[:0]
// 反转:
for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
// 拷贝注意共享底层

make 与容量

make 创建切片并预分配容量。避免频繁扩容。cap 容量增长策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 长度 0 容量 5:
nums := make([]int, 0, 5)
// 预分配避免多次拷贝:
nums = append(nums, 1, 2, 3)
// 容量增长:
// append 超容量时翻倍扩容
// 大切片用预分配:
// make([]int, 0, 100000)
// 长度与容量:
s := make([]int, 5) // 长度 5,值零
cap(s) // 5
// 清空保留容量:
s = s[:0]
// 判断空:
if len(s) == 0
// 用 cap 预分配优化性能

map

map 键值对。make 创建、读、写、删、遍历。nil map 只读不写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
// 读取:
v := m["a"] // 不存在返回零值
v, ok := m["a"] // ok 判断存在
if val, ok := m["x"]; ok {
fmt.Println(val)
}
// 删除:
delete(m, "a")
// 遍历(无序):
for k, v := range m {
fmt.Println(k, v)
}
// 长度:len(m)
// 字面量:
mm := map[string]int{"a": 1}
// nil map:
// var mm map[string]int
// mm["a"] = 1 // panic

map 惯用法

map 做集合、计数、缓存。键值对收集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 集合(set):
seen := make(map[string]bool)
for _, v := range list {
if !seen[v] {
seen[v] = true
fmt.Println("新:", v)
}
}
// 计数:
counts := make(map[string]int)
for _, w := range words {
counts[w]++
}
// 缓存:
var cache = make(map[string][]byte)
func get(key string) ([]byte, bool) {
data, ok := cache[key]
return data, ok
}
// 结构体值:
m := map[string]Point{}
// 更新嵌套:
// 值拷贝陷阱:p := m["a"]; p.X=1; m["a"]=p

排序

sort 包排序 slice。自定义比较函数。二分查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import "sort"
// 基础排序:
nums := []int{3, 1, 2}
sort.Ints(nums)
strs := []string{"b", "a"}
sort.Strings(strs)
// 自定义比较:
type Person struct{ Name string; Age int }
people := []Person{{Age: 30}, {Age: 20}}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
// 逆序:
sort.Slice(people, func(i, j int) bool {
return people[i].Age > people[j].Age
})
// 稳定排序:
sort.SliceStable(people, ...)
// 二分查找:
// sort.SearchInts(nums, 2)
// 判断有序:sort.IntsAreSorted(nums)

container 包

container/heap 堆、container/list 链表。数据结构需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// container/list(链表):
import "container/list"
l := list.New()
l.PushBack(1)
l.PushFront(0)
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
// 堆:
import "container/heap"
// 实现 heap.Interface:
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() any {
old := *h; n := len(old); x := old[n-1]
*h = old[:n-1]; return x
}
// 使用:
h := &IntHeap{3, 1, 2}
heap.Init(h)
heap.Pop(h) // 最小 1

遍历技巧

range 遍历、索引/值、map 键排序遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 索引遍历:
for i := range nums {
nums[i] *= 2
}
// 索引 + 值:
for i, v := range nums {
fmt.Println(i, v)
}
// 只值:
for _, v := range nums
// 字符串 rune:
for i, r := range s
// map 键排序遍历:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
// 倒序遍历切片:
for i := len(nums) - 1; i >= 0; i--

9.内存与性能

垃圾回收、逃逸分析、分配优化与内存分析。

垃圾回收

Go 自动 GC 无需手动释放。并发 GC 低延迟。内存由运行时管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Go 使用并发标记清除 GC
// 无需手动 malloc/free
// 分配:
// 堆对象由 GC 回收
// 栈对象函数返回即释放
// GC 参数:
// GOGC=100 默认阈值
// debug.SetGCPercent()
// 触发:
// 内存翻倍/显式 runtime.GC()
// 影响:
// 减少堆分配可降 GC 压力
// 大量小对象用 sync.Pool
// 大对象注意生命周期

逃逸分析

编译器决定变量分配位置。取地址/接口装箱可能逃逸到堆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 栈分配:
func local() int {
x := 42 // 不逃逸,栈上
return x
}
// 堆分配(逃逸):
func leak() *int {
x := 42
return &x // 地址被返回,逃逸
}
// 查看逃逸:
// go build -gcflags='-m' ./...
// 逃逸触发:
// 1. 返回局部变量地址
// 2. 闭包捕获变量
// 3. 接口存储值(装箱)
// 4. 全局变量赋值
// 优化:
// 避免不必要逃逸减少 GC 压力
// 但不必过度优化

分配优化

预分配容量、复用缓冲、减少小对象。热路径优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 预分配 slice:
nums := make([]int, 0, n) // 避免反复扩容
// 复用缓冲:
var buf bytes.Buffer
buf.Grow(1024) // 预分配
// 避免临时对象:
// 循环内避免 fmt.Sprintf 创建
// 用 builder 拼接:
var sb strings.Builder
// 对象池:
// 复用短生命周期对象
// 接口调用开销:
// 避免热路径接口转发
// 分析先于优化:
// 用 pprof 找出热点
// 别过早优化普通代码

终结器

runtime.SetFinalizer 对象回收前清理。清理外部资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "runtime"
type Resource struct {
name string
handle *os.File
}
func NewResource(name string) *Resource {
r := &Resource{name: name}
runtime.SetFinalizer(r, func(r *Resource) {
if r.handle != nil {
r.handle.Close() // 兜底清理
}
})
return r
}
// 注意事项:
// 终结器不保证立即执行
// 程序退出时未决终结器不保证运行
// 最佳实践:
// 显式 Close 为主
// 终结器仅作兜底
// 循环引用可导致终结器永远不跑

内存模型

并发下内存可见性。channel/锁/atomic 提供同步保证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Go 内存模型:
// 一个 goroutine 写入在
// 另一 goroutine 读取前可见,
// 需通过同步原语:
// 1. channel:
ch := make(chan int)
gr() { ch <- 1 }() // 写
<-ch // 读
// 2. 锁:
var mu sync.Mutex
mu.Lock()
// 临界区
mu.Unlock()
// 3. atomic:
var x atomic.Int64
x.Store(1)
v := x.Load()
// 无同步的共享读写是数据竞争
// 用 go run -race 检测

内存分析

pprof 分析内存与 CPU。堆快照定位泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
_ "net/http/pprof"
"net/http"
)
// 启动 pprof:
// go run -race main.go
// 在代码中:
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// ...
}
// 分析:
// go tool pprof http://localhost:6060/debug/pprof/heap
// go tool pprof http://localhost:6060/debug/pprof/profile
// 命令行交互:
// top / list / web
// 火焰图:
// go tool pprof -http=:8080 profile.out
// 定位:分配热点、泄漏对象

sync.Pool

Pool 复用临时对象,降低 GC 压力。适合短生命周期大对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "sync"
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
// 使用:
b := bufPool.Get().(*bytes.Buffer)
b.Reset() // 复用前清理
// 写入使用...
bufPool.Put(b) // 归还
// 适用:
// 大量短生命周期对象
// 临时缓冲、序列化缓冲
// 注意:
// GC 会清空 Pool(不保证保留)
// 不适用:长期持有对象
// Get 可能返回 nil(New 未设)
// 别把有状态对象放进 Pool

栈与递归

goroutine 栈动态增长。递归深度受栈限制。大递归用迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// goroutine 栈:
// 初始 2KB,动态增长
// 栈上限约 1GB(64 位)
// 递归:
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
// 深递归风险:
// 栈溢出崩溃:fatal error: stack overflow
// 大递归改迭代:
func factorialIter(n int) int {
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result
}
// 递归深度日志:
// 数万层注意栈增长

10.结构体与方法

Go 的 OOP:struct、接口、嵌入与组合(无类继承)。

struct 与方法

struct 组合数据,方法绑定类型。构造惯用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Account struct {
ID string
Balance float64
}
// 值接收者:
func (a Account) Balance() float64 {
return a.Balance
}
// 指针接收者:
func (a *Account) Deposit(amount float64) {
a.Balance += amount
}
// 构造:
func NewAccount(id string) *Account {
return &Account{ID: id}
}
// 使用:
acc := NewAccount("a1")
acc.Deposit(100)
// 值/指针自动转换:
// acc 指针可调用值方法

接收者选择

值接收者不修改原值。指针接收者修改原值。同类型方法接收者需一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Counter struct {
N int
}
// 值接收者(副本):
func (c Counter) Value() int {
return c.N
}
// 指针接收者(修改):
func (c *Counter) Inc() {
c.N++
}
// 使用:
c := Counter{}
c.Inc() // 自动取地址
// 规则:
// 修改接收者 -> 指针
// 大结构体 -> 指针(省复制)
// 全部方法接收者一致:
// 混用会导致某些自动取址
// 接口实现也要求接收者匹配

接口

接口定义行为契约。类型实现接口的方法集即满足接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Greeter interface {
Greet() string
}
// 类型实现:
type Dog struct{ Name string }
func (d Dog) Greet() string {
return "汪汪 " + d.Name
}
type Cat struct{ Name string }
func (c Cat) Greet() string {
return "喵 " + c.Name
}
// 使用:
func GreetAll(gs []Greeter) {
for _, g := range gs {
fmt.Println(g.Greet())
}
}
// 实现是隐式的:
// 只要方法集匹配即实现
// 无需声明 implements
// 接口值包含具体类型
// 接口作为参数解耦

接口与 nil

nil 接口 ≠ 含 nil 指针的接口。判空需反射或仔细处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Error interface {
Error() string
}
// 陷阱:
func returnsNil() *MyError {
return nil
}
var err error = returnsNil()
// err != nil 但内部是 nil 指针
if err != nil {
fmt.Println("有错误") // 误判
}
// 原因:接口 = 类型 + 值
// nil 指针也有类型信息
// 解决:
// 1. 返回 error 而非 *T
// 2. 判空用反射:
// reflect.ValueOf(err).IsNil()
// 3. 约定接口内不放 nil 指针
// 确保返回 error 时 nil 是 error 的 nil

嵌入

结构体嵌入字段提升方法。组合优于继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Base struct {
ID int
}
func (b Base) GetID() int { return b.ID }
type User struct {
Base // 嵌入
Name string
}
// 提升:
u := User{Base: Base{ID: 1}, Name: "Nick"}
u.GetID() // 提升方法
u.ID // 提升字段
// 覆盖:
type Admin struct {
Base
Permissions []string
}
func (a Admin) GetID() int {
return a.Base.ID * 100 // 自定义
}
// 嵌入 interface 也可:
// type Reader interface { io.Reader }
// 组合替代继承:
// 字段 + 方法提升

组合

Go 用组合而非继承组织复用。小接口组合大接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接口组合:
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
type ReadWriter interface {
Reader
Writer
}
// 结构体组合:
type File struct {
name string
}
type GzipFile struct {
File // 复用文件逻辑
level int
}
// 组合提供复用,无继承的脆弱性
// 小接口原则:
// 接口越小越容易满足
// 需要时才组合
// 结构嵌套实现功能复用

类型断言

接口转具体类型。ok 模式安全断言。type switch 多分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var v any = "hello"
// 安全断言:
s, ok := v.(string)
if ok {
fmt.Println(s)
}
// 不安全断言(失败 panic):
// s := v.(string)
// type switch:
switch val := v.(type) {
case string:
fmt.Println("string", val)
case int:
fmt.Println("int", val)
default:
fmt.Println("未知", val)
}
// 断言失败返回零值 + false
// 接口断言到更具体接口:
// if r, ok := w.(io.Reader); ok

any / 空接口

any(interface{})表示任意类型。存储值后需断言使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// any 是 interface{} 别名:
var v any
v = 42
v = "str"
// 取出需断言:
s := v.(string)
// 泛型替代:
// 需要类型参数用泛型:
func Print[T any](v T) {
fmt.Println(v)
}
// 空接口做容器:
var store = make(map[string]any)
store["age"] = 30
store["name"] = "Nick"
// 取出时类型断言:
age, ok := store["age"].(int)
// 慎用:
// 丢失类型安全
// 优先明确类型/泛型
// 序列化时 JSON 用 any 常见

11.错误处理

error 接口、错误包装、errors.Is/As、panic/recover。

error 接口

error 是内建接口:Error() string。返回错误而非抛异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type error interface {
Error() string
}
// 返回错误:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数为零")
}
return a / b, nil
}
// 判断:
result, err := divide(10, 0)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result)
// 自定义错误类型:
type ValidationError struct {
Field string
}
func (e ValidationError) Error() string {
return "字段 " + e.Field + " 无效"
}
// nil 错误表示成功

返回错误

错误沿调用链返回。检查 err != nil 并处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func openConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err // 透传
}
if len(data) == 0 {
return nil, errors.New("配置为空")
}
return data, nil
}
// 调用链:
func load() error {
data, err := openConfig("app.json")
if err != nil {
return err // 继续上传
}
_ = data
return nil
}
// 惯例:
// 出错返回零值 + err
// err 为 nil 时其他返回值有效
// 别吞错误:
// 至少 log 或返回

错误信息

fmt.Errorf 生成带格式的错误。errors.New 简单错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "fmt"
// 简单错误:
// errors.New("失败")
// 带参数:
func open(name string) error {
return fmt.Errorf("无法打开 %s", name)
}
// 多层包装:
// %w 包装底层错误
func load() error {
err := readFile()
if err != nil {
return fmt.Errorf("load 失败: %w", err)
}
return nil
}
// %v 不带包装:
// fmt.Errorf("失败: %v", err)
// 新错误实例:
// errors.New("message")
// 自定义:
// fmt.Errorf("状态码 %d", 404)

errors.Is

errors.Is 沿包装链判断错误是否匹配目标。取代 err == 判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "errors"
var ErrNotFound = errors.New("not found")
func find(id int) error {
return fmt.Errorf("查找 %d: %w", id, ErrNotFound)
}
// 判断(支持包装):
err := find(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("未找到")
}
// 多目标:
// errors.Is(err, ErrNotFound) || errors.Is(err, ErrTimeout)
// 单一目标:
// 定义哨兵错误 var ErrX = errors.New(...)
// == 无法穿透 %w 包装
// 标准库用法:
// errors.Is(err, os.ErrNotExist)

errors.As

errors.As 沿包装链查找特定类型错误并提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "errors"
type ConfigError struct {
Path string
Msg string
}
func (e *ConfigError) Error() string {
return e.Path + ": " + e.Msg
}
// 包装抛出:
func loadConfig() error {
return fmt.Errorf("加载: %w",
&ConfigError{Path: "app.json", Msg: "语法错误"})
}
// 提取:
err := loadConfig()
var configErr *ConfigError
if errors.As(err, &configErr) {
fmt.Println(configErr.Path, configErr.Msg)
}
// 与 Is 区别:
// Is 比较错误值(哨兵)
// As 提取错误类型(含字段)
// 自定义错误指针接收者
// 常用于第三方库错误

panic 与 recover

panic 程序崩溃前恢复。recover 在 defer 中捕获。仅用于不可恢复错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// panic:
func divide(a, b int) int {
if b == 0 {
panic("除数为零") // 崩溃
}
return a / b
}
// recover 捕获:
func safeCall(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
f()
return nil
}
// 使用:
// err := safeCall(func() { divide(1, 0) })
// 规则:
// panic 只在 defer 中 recover
// 仅用于程序无法继续的错误
// 别用 panic 做常规错误处理
// 库代码尽量避免 panic

defer 错误处理

defer 中检查错误。命名返回值让 defer 可改返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 关闭文件检查错误:
func read() (err error) {
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer func() {
if closeErr := f.Close(); closeErr != nil {
err = closeErr // 覆盖返回值
}
}()
// 处理...
return nil
}
// 关闭连接:
// defer conn.Close()
// 解锁:
// mu.Lock()
// defer mu.Unlock()
// 清理临时文件:
// defer os.Remove(tmp)
// defer 参数立即求值注意

组合错误

errors.Join 合并多个错误为一个。多任务集中检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "errors"
// 合并多个错误:
err1 := errors.New("数据库失败")
err2 := errors.New("缓存失败")
joined := errors.Join(err1, err2)
fmt.Println(joined)
// 两条错误各占一行
// 判断任一错误:
// errors.Is(joined, err1) // true
// 全 nil 返回 nil:
// errors.Join(nil, nil) // nil
// 收集任务错误:
func runAll() error {
var errs []error
if err := step1(); err != nil {
errs = append(errs, err)
}
if err := step2(); err != nil {
errs = append(errs, err)
}
return errors.Join(errs...)
}
// Go 1.20+
// 错误可同时 is 多个目标

12.输入输出

文件读写、bufio、JSON、io.Reader/Writer。

文件读取

os.ReadFile 读取整个文件。os.Open + Reader 逐行/流式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "os"
// 整个文件:
data, err := os.ReadFile("data.txt")
if err != nil {
log.Fatal(err)
}
// 逐行读取:
f, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
// 大文件逐行不占内存
// 分块读取:
// 用 io.Reader + Read 固定缓冲

文件写入

os.WriteFile 整体写入。os.OpenFile 追加。缓冲写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "os"
// 整体写入:
err := os.WriteFile("out.txt",
[]byte("hello"), 0644)
// 追加:
f, _ := os.OpenFile("log.txt",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
f.WriteString("更多内容\n")
// 缓冲写入:
w := bufio.NewWriter(f)
w.WriteString("text")
w.Flush() // 刷新到文件
// 权限:
// 0644 属主读写,他人读
// 创建目录:os.MkdirAll
// 错误处理别忽略

JSON 序列化

json.Marshal 结构体转 JSON。字段标签控制字段名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import "encoding/json"
type User struct {
Name string `json:"name"`
Age int `json:"age"`
// 忽略空值:
Email string `json:"email,omitempty"`
// 忽略字段:
Secret string `json:"-"`
}
// 序列化:
u := User{Name: "Nick", Age: 30}
data, err := json.Marshal(u)
// 美化:
pretty, _ := json.MarshalIndent(u, "", " ")
// 转 map:
m := map[string]any{"k": "v"}
b, _ := json.Marshal(m)
// 默认字段名:
// 导出字段名首字母小写?否,保持原名
// 用标签控制输出字段名

JSON 反序列化

json.Unmarshal 解析 JSON 到结构体/map。Decoder 流式解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import "encoding/json"
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
// 解析字符串:
var u User
err := json.Unmarshal([]byte(`{"name":"Nick","age":30}`), &u)
// 解析到 map:
var m map[string]any
json.Unmarshal(data, &m)
// 数字在 map 里是 float64:
age := m["age"].(float64)
// 流式解析(大 JSON):
dec := json.NewDecoder(bytes.NewReader(data))
dec.Decode(&u)
// 忽略未知字段:
// Decoder.DisallowUnknownFields() 反向
// 错误处理:
// 语法错误返回 *json.SyntaxError

io.Reader / Writer

io.Reader/Writer 抽象流。io.ReadAll、io.Copy 便捷函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"io"
"strings"
)
// 从 Reader 读全部:
r := strings.NewReader("hello")
data, _ := io.ReadAll(r)
// 复制流:
// io.Copy(dst, src)
// 流式管道:
// io.Pipe()
// 组合:
// bufio.NewReader / bufio.NewWriter
// 有限读取:
// io.LimitReader(r, 100)
// 多读取器:
// io.MultiReader(a, b)
// 多写入器:
// io.MultiWriter(a, b)
// 空实现:
// io.Discard 丢弃写入

bufio 缓冲

bufio 缓冲读写减少系统调用。Scanner 逐行、ReadString。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
"bufio"
"os"
)
// 读取:
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
// 读取固定字节:
buf := make([]byte, 1024)
reader.Read(buf)
// Scanner 逐行:
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
fmt.Println(sc.Text())
}
// 写入:
w := bufio.NewWriter(os.Stdout)
w.WriteString("hello\n")
w.Flush()
// 缓冲大小:
// bufio.NewReaderSize(r, 64*1024)
// Scanner 默认行上限 64K:
// sc.Buffer(buf, max)
// 性能:减少系统调用

标准流

os.Stdin/Stdout/Stderr。fmt.Scan 输入。命令行数据处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
"bufio"
"fmt"
"os"
)
// 打印:
fmt.Println("hello")
// 到 stderr:
fmt.Fprintln(os.Stderr, "error")
// 读取输入:
var name string
fmt.Scanln(&name)
// 格式化扫描:
var age int
fmt.Scanf("%d", &age)
// 逐行处理 stdin:
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
fmt.Println(sc.Text())
}
// 管道用法:
// cat data | go run app.go
// 输出重定向:
// go run app.go > out.txt

路径操作

filepath 跨平台路径处理。Join、Dir、Base、Ext。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
"path/filepath"
"fmt"
)
// 拼接路径:
path := filepath.Join("dir", "sub", "a.txt")
// 目录/文件名/扩展名:
filepath.Dir(path) // dir/sub
filepath.Base(path) // a.txt
filepath.Ext(path) // .txt
// 清理多余分隔符:
filepath.Clean("dir//./a.txt")
// 绝对路径:
abs, _ := filepath.Abs(path)
// 相对路径:
rel, _ := filepath.Rel("/a", "/a/b/c")
// 分隔符:
// Windows \\ Unix /
// filepath.Separator
// 匹配遍历:
// filepath.Glob("data/*.json")
// 递归遍历:filepath.WalkDir
// 不要硬编码分隔符

13.常见误区

Go 日常开发最容易踩的坑与正确写法。

nil 与空 slice

nil slice 与空 slice 不同。JSON 序列化结果不同。用 make 明确意图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BAD:nil slice 序列化为 null
var s []string
json.Marshal(s) // "null"
// GOOD:空 slice 序列化为 []
s := make([]string, 0)
json.Marshal(s) // "[]"
// nil slice 可 append:
var n []int
n = append(n, 1) // 正常
// 判断:
// len(s) == 0 对两者都成立
// 返回集合惯例:
// 明确返回空 slice:
func list() []string {
return make([]string, 0)
}

循环变量捕获

goroutine 闭包捕获循环变量(1.22 前共享)。1.22+ 每次迭代独立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// BAD(1.22 前):goroutine 全打印最后值
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 可能全 3
}()
}
// GOOD:显式传参
for i := 0; i < 3; i++ {
go func(n int) {
fmt.Println(n)
}(i)
}
// 1.22+ 循环变量每次迭代新变量:
// for i := range 3 { go ... } 安全
// 旧代码兼容写法:
// 局部拷贝 x := i
// 传参最清晰

map 遍历无序

map 遍历顺序随机。依赖顺序需排序键。range map 顺序不可预测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// BAD:依赖 map 遍历顺序
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
fmt.Println(k, v) // 顺序随机
}
// GOOD:键排序遍历
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
// 原因:map 哈希实现无顺序
// 输出/JSON/测试需稳定顺序
// slice 遍历保持插入顺序

字符串修改

字符串不可变,不能直接改。转 []rune/[]byte 修改后转回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:不能直接改字符串
s := "hello"
// s[0] = 'H' 错误:cannot assign
// GOOD:转可变类型
bs := []byte(s)
bs[0] = 'H'
s = string(bs) // "Hello"
// 中文需 []rune:
rs := []rune("你好")
rs[0] = '再'
s = string(rs) // "再好"
// 拼接:
// + 或 strings.Builder
// 字符串转换有复制开销
// 大字符串注意内存

切片复制与共享

切片赋值共享底层数组。修改互相影响。copy 才复制元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:误以为赋值复制了数据
original := []int{1, 2, 3}
copySlice := original
copySlice[0] = 99
fmt.Println(original) // [99 2 3] !
// GOOD:copy 复制
// copy 目标需足够容量
dst := make([]int, len(original))
copy(dst, original)
// 或 append 模式:
copySlice := append([]int{}, original...)
// append 可能共享:
// 扩容时才复制
// 函数返回 slice 注意共享
// 修改前确认所有权

忽略错误

忽略 err 导致静默失败。至少记录或显式 _ =。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BAD:忽略错误
f, _ := os.Open("data.txt") // 失败后 f nil
f.Read(...) // 空指针 panic
// BAD:裸忽略
json.Unmarshal(data, &u) // 不检查
// GOOD:检查并处理
f, err := os.Open("data.txt")
if err != nil {
log.Fatalf("打开失败: %v", err)
}
defer f.Close()
// 显式忽略意图:
_, _ = fmt.Println("x") // 少见
// 惯例:
// 所有 err != nil 都处理
// 记录上下文再返回

接收者不一致

同类型方法混用值/指针接收者。接口实现不匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:混用接收者
func (c Counter) Get() int { return c.N }
func (c *Counter) Inc() { c.N++ } // 不一致
// 部分方法只有指针实现:
// 值类型不满足接口
// GOOD:统一指针接收者
func (c *Counter) Get() int { return c.N }
func (c *Counter) Inc() { c.N++ }
// 接口实现:
var _ CounterIface = &Counter{} // 指针
// 编译期断言:
// var _ Shape = (*Circle)(nil)
// 可修改对象统一指针接收者
// 小不可变对象值接收者

变量遮蔽

:= 在内层作用域创建新变量,遮蔽外层。作用域不一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// BAD:err 被遮蔽
func f() error {
x, err := compute()
if err != nil {
return err
}
if y, err := other(); err != nil {
// err 是内层新变量
return err
}
_ = x
return nil
}
// 遮蔽导致外层 err 未赋值
// 检测:
// go vet 会报告 shadow
// 或 -gcflags
// GOOD:复用 err:
// y, err := other() 已声明可复用
// 规则:
// 内层需修改外层变量用 =
// 新变量用 :=

循环中 defer

循环内 defer 延迟到函数返回,资源堆积。用函数包装或立即处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// BAD:循环内 defer 堆积
for _, f := range files {
fh, err := os.Open(f)
if err != nil {
continue
}
defer fh.Close() // 全部到函数结束
}
// 大量文件打开不关闭
// GOOD:函数包装
for _, f := range files {
err := processFile(f)
if err != nil {
log.Println(err)
}
}
func processFile(name string) error {
fh, err := os.Open(name)
if err != nil {
return err
}
defer fh.Close() // 每次调用结束关闭
return nil
}
// 或立即 close

14.并发

goroutine、channel、select 与并发安全。

goroutine

go 关键字启动并发函数。轻量级,栈动态增长。主函数退出程序即结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 启动 goroutine:
go fmt.Println("并发执行")
// 匿名函数:
go func() {
fmt.Println("后台任务")
}()
// 主函数不等待 goroutine:
// 程序退出所有 goroutine 终止
// 等待完成用 WaitGroup 或 channel
// 例:
go func(n int) {
fmt.Println(n * 2)
}(10)
// 注意:
// 别依赖 goroutine 执行顺序
// 错误处理在 goroutine 内完成

channel

channel 是 goroutine 通信管道。无缓冲 channel 收发同步阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建 channel:
ch := make(chan int)
// 发送:
ch <- 42
// 接收:
value := <-ch
// 无缓冲同步:
// 收发双方都阻塞直到配对
// 关闭 channel:
close(ch)
// 接收判断关闭:
v, ok := <-ch
if !ok {
fmt.Println("已关闭")
}
// 只读/只写:
// func f(ch <-chan int) // 只读
// func g(ch chan<- int) // 只写
// 单向 channel 限制使用面

缓冲 channel

make(chan T, n) 创建有缓冲 channel。满则阻塞发送、空则阻塞接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 缓冲 2:
ch := make(chan int, 2)
ch <- 1 // 不阻塞
ch <- 2 // 不阻塞
// ch <- 3 // 缓冲满,阻塞
// 取走腾出空间:
<-ch
// 用途:
// 任务队列:
work := make(chan Task, 10)
// 信号量限流:
// make(chan struct{}, 5)
// 生产者-消费者解耦
// 注意事项:
// 缓冲只影响阻塞时机
// 关闭后发送会 panic
// 接收缓冲区内的值后再见关闭

select

select 多路等待 channel。随机选择就绪 case。default 非阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
select {
case v := <-ch1:
fmt.Println("ch1", v)
case v := <-ch2:
fmt.Println("ch2", v)
case ch3 <- 1:
fmt.Println("发送成功")
default:
fmt.Println("无就绪,非阻塞")
}
// 多个就绪随机选一个
// 空 select 永久阻塞:
// select {}
// 超时模式:
select {
case v := <-data:
fmt.Println(v)
case <-time.After(2 * time.Second):
fmt.Println("超时")
}
// 循环 select 处理多流

WaitGroup

sync.WaitGroup 等待一组 goroutine 完成。Add/Done/Wait。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "sync"
var wg sync.WaitGroup
// 启动任务:
for i := 0; i < 5; i++ {
wg.Add(1) // 计数 +1
go func(n int) {
defer wg.Done() // 完成 -1
fmt.Println(n)
}(i)
}
// 等待全部完成:
wg.Wait()
// 规则:
// Add 需在 goroutine 前调用
// Done 用 defer 保证执行
// Wait 后不能复用计数
// 大量任务在循环外 Add(n)
// 别在 Add 后复制 WaitGroup

互斥锁

sync.Mutex 保护共享数据。Lock/Unlock。读多写少用 RWMutex。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "sync"
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock() // 保证解锁
balance += amount
}
func Balance() int {
mu.Lock()
defer mu.Unlock()
return balance
}
// 读写锁:
// var rw sync.RWMutex
// rw.RLock() 读锁
// rw.RUnlock()
// 读可并发,写独占
// 防止死锁:
// 统一加锁顺序
// 别在持锁时调用耗时操作

sync.Once

sync.Once 保证函数只执行一次。懒初始化单例。并发安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "sync"
var (
once sync.Once
instance *Config
)
// 单例模式:
func GetConfig() *Config {
once.Do(func() {
instance = &Config{
Env: os.Getenv("APP_ENV"),
}
// 只执行一次
})
return instance
}
// 特点:
// 多 goroutine 并发安全
// 只执行一次(无论成败)
// 用于:
// 数据库连接、缓存加载
// 注册表初始化
// 与 init 区别:
// init 在包加载时必然执行
// Once 按需、可被测试重置

数据竞争检测

go run -race 检测数据竞争。并发读写同一变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 检测:
// go run -race main.go
// go test -race ./...
// 竞争示例:
var counter int
for i := 0; i < 1000; i++ {
go func() {
counter++ // 无锁,竞争
}()
}
// 输出:
// WARNING: DATA RACE
// 修复:
// 用锁、channel 或 atomic:
var c atomic.Int64
c.Add(1)
// 最佳实践:
// 测试常开 -race
// CI 集成 race 检查
// 别共享值,用消息传递

15.网络

HTTP 请求、服务端、context 与 TCP。

HTTP GET

http.Get 发起 GET 请求。defer Body.Close 关闭响应体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "net/http"
resp, err := http.Get("https://api.example.com/users")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 必须关闭
// 检查状态码:
if resp.StatusCode != http.StatusOK {
log.Fatalf("状态码 %d", resp.StatusCode)
}
// 读取响应体:
body, err := io.ReadAll(resp.Body)
fmt.Println(string(body))
// 响应头:
contentType := resp.Header.Get("Content-Type")
// 注意:
// 默认无超时,用 http.Client
// 大响应限制读取量

HTTP 服务端

http.HandleFunc 注册路由。ListenAndServe 监听端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path)
}
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"ok": true}`)
})
// 监听 8080:
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 读取表单:r.FormValue("name")
// 返回 JSON:json.NewEncoder(w).Encode(v)
// 优雅退出:http.Server + Shutdown

HTTP 客户端

http.Client 配置超时与连接池。http.NewRequest 自定义请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
"net/http"
"time"
)
// 带超时的客户端:
client := &http.Client{
Timeout: 10 * time.Second,
}
// 自定义请求:
req, _ := http.NewRequest("POST",
"https://api.example.com",
strings.NewReader(`{"name":"go"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token")
// 发送:
resp, err := client.Do(req)
// 连接池复用:
// 全局共享一个 Client
// Transport 配置:
// MaxIdleConnsPerHost
// 超时类型:
// Timeout 包含读取响应

JSON API

请求 JSON API 并解码。自定义结构体字段标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func fetchUsers() ([]User, error) {
resp, err := http.Get("https://api.example.com/users")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("状态码 %d", resp.StatusCode)
}
var users []User
if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
return nil, err
}
return users, nil
}
// 大响应用 Decoder 流式解码
// 未知字段默认忽略
// 服务端返回字段名匹配标签

context

context 传递取消信号与超时。WithTimeout/WithCancel。贯穿网络调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "context"
// 带超时:
ctx, cancel := context.WithTimeout(
context.Background(), 2*time.Second)
defer cancel() // 释放资源
// 发起带 context 的请求:
req, _ := http.NewRequestWithContext(
ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
// 可取消:
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(100 * time.Millisecond)
cancel() // 取消所有子任务
}()
// 传递:
// context 作为函数第一参数
// 禁止存到结构体字段
// 派生:WithValue 传请求元数据

URL 解析

url.Parse 解析 URL。Query 读取参数。URL 构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import "net/url"
u, err := url.Parse(
"https://user:[email protected]/path?q=go#sec")
// 各段:
u.Scheme // "https"
u.Host // "example.com"
u.Path // "/path"
u.Fragment // "sec"
// 查询参数:
q := u.Query()
q.Get("q") // "go"
// 构建 URL:
base, _ := url.Parse("https://api.com")
base.Path = "/v1/users"
params := url.Values{}
params.Set("page", "2")
base.RawQuery = params.Encode()
// 编码:
// url.QueryEscape / PathEscape
// 拼接用 ResolveReference

TCP Socket

net.Dial 客户端连接。net.Listen 服务端监听。原始字节流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
"net"
"bufio"
)
// 客户端:
conn, err := net.Dial("tcp", "example.com:80")
defer conn.Close()
// 发送 HTTP 请求:
fmt.Fprintf(conn,
"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
// 读取响应:
resp, _ := bufio.NewReader(conn)
.ReadString('\n')
fmt.Println(resp)
// 服务端:
ln, _ := net.Listen("tcp", ":8080")
for {
conn, _ := ln.Accept()
go handleConn(conn) // 每连接一个 goroutine
}
// 写入:conn.Write([]byte)
// 设置超时:conn.SetDeadline

路由增强

Go 1.22+ ServeMux 支持方法与路径参数。http.ServeMux 模式匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "net/http"
// Go 1.22+ 路由:
mux := http.NewServeMux()
// 方法 + 路径:
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)
// 路径参数:
mux.HandleFunc("GET /users/{id}", getUser)
// 通配符:
mux.HandleFunc("/files/{path...}", serveFile)
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // 取参数
fmt.Fprintf(w, "用户 %s", id)
}
// 旧写法:
// http.HandleFunc("/users/", handler)
// 手动 r.URL.Path 解析
// 方法不匹配返回 405

16.时间与日期

time 包、Duration、格式化与时区。

获取时间

time.Now 返回 Time 值。可获取各字段。UTC/Local。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "time"
now := time.Now() // 本地时间
// 各字段:
now.Year() // 2026
now.Month() // August
now.Day() // 2
now.Hour()
now.Minute()
now.Second()
now.Weekday() // Sunday
// UTC:
t := time.Now().UTC()
// Unix 时间戳:
unix := time.Now().Unix() // 秒
milli := time.Now().UnixMilli() // 毫秒
// 特定时间:
// time.Date(2026, 8, 2, 10, 0, 0, 0, time.UTC)
// 比较:t.Before(u) / t.After(u)

Duration

time.Duration 是纳秒整数。常量单位、加减、睡眠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "time"
// 常量:
// time.Nanosecond
// time.Microsecond
// time.Millisecond
// time.Second
// time.Minute
// time.Hour
// 定义时长:
d := 2 * time.Second
// 转换:
ms := d.Milliseconds()
ns := d.Nanoseconds()
// 格式化:d.String() // "2s"
// 解析:
d, _ = time.ParseDuration("1h30m")
// 计算:
// t.Add(d) / t.Sub(u)
// t.AddDate(0, 1, 0) 加一个月
// 等待:
// time.Sleep(d)
// 比较:
// d > time.Minute
// 类型安全:不能直接 int 运算

格式化

Time.Format 按布局格式化。Go 用参考时间 2006-01-02 15:04:05。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "time"
t := time.Now()
// 标准格式:
t.Format("2006-01-02") // 2026-08-02
t.Format("15:04:05") // 14:30:22
t.Format("2006-01-02 15:04") // 完整
t.Format("2006-01-02 15:04:05 MST") // 带时区
// 参考时间固定:
// 01月 02日 03时 04分 05秒
// 06年 07月 MST
// 2006 年是关键标记
// 预定义:
// time.RFC3339:"2006-01-02T15:04:05Z07:00"
// time.RFC1123 / time.Kitchen
// 布局错误导致空字符串

解析

time.Parse 解析字符串为 Time。布局需与参考时间匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "time"
// 按布局解析:
t, err := time.Parse("2006-01-02", "2026-08-02")
// 含时区:
// time.Parse(time.RFC3339, "2026-08-02T10:00:00Z")
// 本地时区解析:
// time.ParseInLocation(layout, value, time.Local)
// 常见错误:
// 布局写错返回零值
// 月份/日期必须用参考值
// 用法:
// time.Parse("2006/01/02 15:04", "2026/08/02 10:30")
// 时区处理:
// 无时区按 UTC 解析
// 需求本地时区用 ParseInLocation

Ticker

time.Ticker 定时触发。定期任务。Stop 停止,defer 关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "time"
// 每秒触发:
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() // 释放
// 定期执行:
for range ticker.C {
fmt.Println("tick", time.Now())
}
// 手动控制:
// 只消费一次:
// <-ticker.C
// 停止检查:
for {
select {
case t := <-ticker.C:
fmt.Println("执行", t)
case <-stop:
return // 退出
}
}
// 注意:
// 忘记 Stop 泄漏定时器
// 无缓冲的 C 迟到即丢弃
// 简写:time.Tick 不释放,慎用

Sleep 与等待

time.Sleep 暂停当前 goroutine。只影响当前 goroutine。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "time"
// 暂停 2 秒:
time.Sleep(2 * time.Second)
// 动态时长:
wait := time.Duration(n) * time.Millisecond
time.Sleep(wait)
// 毫秒值:
time.Sleep(500 * time.Millisecond)
// 注意:
// 阻塞当前 goroutine
// 不用来做精确调度
// 测试中加速:
// 把时长抽成变量方便替换
// 与 Timer 区别:
// Sleep 无条件阻塞
// Timer 可取消
// select 中不用 Sleep 当超时

Timer

time.Timer 单次延时触发。可停止、重置。超时取消。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "time"
// 3 秒后触发:
timer := time.NewTimer(3 * time.Second)
defer timer.Stop()
// 等待触发:
<-timer.C
fmt.Println("定时到")
// 提前取消:
// timer.Stop()
// 判断是否取消:
// 用 select:
select {
case <-timer.C:
fmt.Println("执行")
case <-ctx.Done():
fmt.Println("已取消")
}
// 重置:
// timer.Reset(5 * time.Second)
// 一次性 vs Ticker 重复
// 用于:超时控制、延时任务
// After 是 Timer 的便捷封装

时区

time.LoadLocation 加载时区。In 转换。FixedZone 固定偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "time"
// 加载时区:
loc, err := time.LoadLocation("Asia/Shanghai")
// 转换到该时区:
t := time.Now().In(loc)
// 常用时区:
// time.UTC、time.Local
// "America/New_York"
// "Europe/London"
// "Asia/Tokyo"
// 固定偏移:
fixed := time.FixedZone("CST", 8*3600)
// 特定时区构造:
// time.Date(2026, 8, 2, 10, 0, 0, 0, loc)
// 注意:
// 无 IANA 数据库报错
// Windows 需导入 tzdata:
// import _ "time/tzdata"
// 存时间建议存 UTC

17.进程与环境

命令行参数、环境变量、子进程与退出码。

命令行参数

os.Args 是命令行参数切片。第一个是程序名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
"fmt"
"os"
)
// 全部参数:
// go run app.go a b c
// os.Args = ["app.go", "a", "b", "c"]
for i, arg := range os.Args {
fmt.Printf("args[%d] = %s\n", i, arg)
}
// 仅业务参数:
args := os.Args[1:]
// 简单解析:
if len(args) < 1 {
fmt.Println("用法: app <name>")
os.Exit(1)
}
name := args[0]
// 注意:
// 空格分隔,引号可含空格
// 复杂参数用 flag 包
// 参数是字符串

flag 解析

flag 包解析命令行标志。-name value、-flag。默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import "flag"
// 定义标志:
name := flag.String("name", "Go", "用户名")
count := flag.Int("n", 1, "次数")
verbose := flag.Bool("v", false, "详细输出")
// 解析:
flag.Parse()
// 使用:
for i := 0; i < *count; i++ {
fmt.Printf("%s (%v)\n", *name, *verbose)
}
// 运行:
// go run app.go -name Nick -n 3 -v
// 非标志参数:
rest := flag.Args()
// 自定义用法:
flag.Usage = func() { fmt.Println("用法...") }
// 检查解析错误:
// flag.Parse 遇错自动退出
// 简写:
// flag.StringVar(&s, "x", "", "说明")

环境变量

os.Getenv 读取、os.Setenv 设置环境变量。LookupEnv 判断存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
"fmt"
"os"
)
// 读取:
path := os.Getenv("PATH")
// 带默认值:
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
// 判断是否存在:
val, ok := os.LookupEnv("APP_ENV")
if !ok {
fmt.Println("未设置 APP_ENV")
}
// 设置:
os.Setenv("APP_ENV", "production")
// 清除:
os.Unsetenv("APP_ENV")
// 列出全部:
// os.Environ() // ["KEY=value", ...]
// 展开:os.ExpandEnv("$HOME/x")
// 配置优先用环境变量

退出码

os.Exit 立即终止程序并设退出码。0 成功、非 0 失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import "os"
// 成功退出:
// 正常 return 退出码为 0
// 失败退出:
if err != nil {
fmt.Fprintln(os.Stderr, "失败:", err)
os.Exit(1)
}
// 注意:
// os.Exit 不执行 defer
// 不刷新缓冲输出
// 约定:
// 0 成功
// 1 通用错误
// 2 用法错误
// 64-113 预留/信号
// 库代码别调 os.Exit
// main 里才用
// log.Fatal 也会退出:
// log.Fatalf("错误: %v", err) // 退出码 1

运行子进程

exec.Command 运行外部命令。捕获输出、管道、环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
"os/exec"
"strings"
)
// 运行并取输出:
out, err := exec.Command("ls", "-l").Output()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
// 组合输出(含 stderr):
// cmd.CombinedOutput()
// 捕获 stderr:
var stderr strings.Builder
cmd := exec.Command("go", "version")
cmd.Stderr = &stderr
// 设置环境:
cmd.Env = append(os.Environ(), "FOO=bar")
// 交互输入:
// cmd.Stdin = strings.NewReader("data")
// 流式输出:
// cmd.Stdout = os.Stdout
// 运行状态:cmd.Run()

信号处理

signal.Notify 捕获系统信号。优雅退出。SIGINT/SIGTERM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"os"
"os/signal"
"syscall"
)
// 监听信号:
quit := make(chan os.Signal, 1)
signal.Notify(quit,
syscall.SIGINT, // Ctrl+C
syscall.SIGTERM) // kill
// 等待信号:
<-quit
fmt.Println("收到退出信号,清理资源")
// 保存状态、关连接、停止任务
os.Exit(0)
// 只忽略:
// signal.Ignore(syscall.SIGINT)
// 停止监听:
// signal.Stop(quit)
// 常见:
// 服务优雅关机、命令行工具中断

文件系统

os 目录与文件操作。MkdirAll、RemoveAll、Rename、Stat。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "os"
// 创建目录(含父级):
os.MkdirAll("data/sub", 0755)
// 删除目录树:
os.RemoveAll("data")
// 删除文件:
os.Remove("tmp.txt")
// 重命名/移动:
os.Rename("a.txt", "b.txt")
// 文件信息:
info, err := os.Stat("data.txt")
if err == nil {
fmt.Println(info.Size())
fmt.Println(info.IsDir())
fmt.Println(info.ModTime())
}
// 是否存在:
if _, err := os.Stat("f"); os.IsNotExist(err) {
fmt.Println("不存在")
}
// 临时文件:
// os.CreateTemp("", "prefix-*")
// 遍历目录:os.ReadDir("dir")

工作目录

os.Getwd 获取当前目录。Chdir 切换。绝对路径处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
"os"
"path/filepath"
)
// 当前目录:
cwd, err := os.Getwd()
fmt.Println(cwd)
// 切换:
// os.Chdir("data")
// 拼接路径:
path := filepath.Join(cwd, "config", "app.yaml")
// 清理/解析:
// filepath.Clean(path)
// filepath.Abs(path)
// 常用:
// 配置加载相对路径
// 缓存目录:
// os.UserCacheDir()
// 配置目录:
// os.UserConfigDir()
// 可执行路径:
// os.Executable()
// 注意程序运行目录与源码目录不同

18.正则表达式

regexp 包:编译、匹配、查找、替换与分组。

编译

regexp.Compile 编译模式。MustCompile 编译失败即 panic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "regexp"
// 编译(可返回错误):
re, err := regexp.Compile(`^[a-z]+$`)
if err != nil {
log.Fatal(err)
}
// 固定模式直接 MustCompile:
re = regexp.MustCompile(`\d+`)
// 编译耗时,循环外编译:
// 全局变量:
var emailRe = regexp.MustCompile(
`^[\w.+-]+@[\w-]+\.[\w.]+$`)
// 简化:
// regexp.MatchString 每次编译
// 高频调用用预编译
// 编译后并发安全可复用

匹配判断

MatchString 判断是否匹配。Match 匹配字节切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "regexp"
var digitRe = regexp.MustCompile(`\d+`)
// 判断字符串:
digitRe.MatchString("abc123") // true
// 匹配字节:
digitRe.Match([]byte("abc123")) // true
// 完全匹配:
// 用 ^...$ 包裹模式
re := regexp.MustCompile(`^\d{3}$`)
re.MatchString("123") // true
re.MatchString("1234") // false
// 大小写:
// (?i) 内联标志
// regexp.MustCompile(`(?i)^go$`)
// 说明:
// 默认贪婪匹配
// 空匹配也是匹配
// 用途:表单校验、日志过滤

查找

FindString 返回第一个匹配。FindIndex 返回位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "regexp"
var re = regexp.MustCompile(`\bgo\b`)
// 第一个匹配:
re.FindString("I love go and Go") // "go"
// 字节:re.Find([]byte(s))
// 匹配位置:
idx := re.FindStringIndex("go and go")
// idx = [0 2]
// 全部位置:
all := re.FindAllStringIndex("go and go", -1)
// [[0 2] [8 10]]
// 限制数量:
// re.FindAllString(s, 2) 最多 2 个
// 子匹配字符串:
// re.FindStringSubmatch(s)
// 判断是否有匹配:
// re.FindString(s) != ""

全部匹配

FindAllString 返回所有匹配。计数参数 -1 全部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "regexp"
var re = regexp.MustCompile(`\d+`)
// 全部匹配:
all := re.FindAllString("a1 b22 c333", -1)
// ["1" "22" "333"]
// 限制前 2 个:
re.FindAllString(s, 2)
// 计数:
// len(all)
// 字节版:
// re.FindAll(s, -1)
// 位置:
// re.FindAllStringIndex(s, -1)
// 每组的匹配:
// re.FindAllStringSubmatch(s, -1)
// 例:提取所有邮箱
// 注意:
// 无匹配返回 nil 切片
// -1 表示不限数量

替换

ReplaceAllString 替换所有匹配。$1 引用分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "regexp"
var re = regexp.MustCompile(`\d+`)
// 替换所有数字:
re.ReplaceAllString("a1b2", "#")
// "a#b#"
// 分组引用:
var nameRe = regexp.MustCompile(
`(\w+),\s*(\w+)`)
nameRe.ReplaceAllString("Doe, John", "$2 $1")
// "John Doe"
// 限制次数:
// re.ReplaceAllString(s, "x") 无次数参数
// 用 ReplaceAllLiteralString 不做展开:
// re.ReplaceAllLiteralString(s, "$1")
// 回调:
// re.ReplaceAllStringFunc(s, fn)
// 例:数字加一
// 未匹配返回原字符串

分割

re.Split 按模式分割字符串。按数字/空白/逗号分割。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "regexp"
// 按逗号分割:
var re = regexp.MustCompile(`,`)
parts := re.Split("a,b,c", -1)
// ["a" "b" "c"]
// 按空白分割:
spaces := regexp.MustCompile(`\s+`)
spaces.Split("a b\tc", -1)
// ["a" "b" "c"]
// 限制数量:
re.Split(s, 2) // 最多 2 段
// 注意:
// -1 不限制
// 空匹配分割行为特殊
// 替代:
// strings.Fields 按空白
// strings.Split 按字面
// 模式复杂才用正则
// 分割结果去除空段需自行过滤

分组捕获

括号分组提取子串。FindStringSubmatch 返回分组。命名分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "regexp"
// 捕获组:
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
sub := re.FindStringSubmatch("2026-08-02")
// sub[0] = "2026-08-02" 整体
// sub[1] = "2026"
// sub[2] = "08"
// sub[3] = "02"
// 命名分组:
nameRe := regexp.MustCompile(`(?P<year>\d{4})`)
sub = nameRe.FindStringSubmatch("2026")
idx := nameRe.SubexpIndex("year") // 1
sub[idx] // "2026"
// 全部组:
// re.FindAllStringSubmatch(s, -1)
// 非捕获组:
// (?:...) 不占用索引
// 检查组数:re.NumSubexp()

常见模式

常用正则表达式与语法要点。字符类、量词、锚点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "regexp"
// 邮箱:
regexp.MustCompile(`^[\w.+-]+@[\w-]+\.[\w.]+$`)
// URL:
regexp.MustCompile(`^https?://[\w.-]+`)
// 手机号(中国):
regexp.MustCompile(`^1[3-9]\d{9}$`)
// 身份证号:
regexp.MustCompile(`^\d{17}[\dXx]$`)
// IP 地址:
regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`)
// 语法要点:
// \d 数字 \w 单词 \s 空白
// + 一个或多个 * 零或多个
// {2,4} 次数范围
// ^ 行首 $ 行尾
// 贪婪到非贪婪:\d+? 加 ?
// 反义:\D \W \S
// 字符类:[a-zA-Z] [^0-9]

19.构建与测试

构建、单元测试、静态检查与跨平台编译。

go build

go build 编译包。输出名、tags、静态链接选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 构建当前目录:
// go build
// 指定输出:
// go build -o bin/app .
// 多个包:
// go build ./...
// 编译但不输出(检查):
// go build ./...
// 构建标签:
// go build -tags "jsoniter"
// 减少体积:
// go build -ldflags "-s -w"
// 嵌入版本信息:
// go build -ldflags "-X main.version=1.2.3"
// 交叉编译见下节
// 缓存:
// 增量构建走 build cache
// 验证:
// go build && go vet

单元测试

go test 运行 *_test.go。testing 包。t.Error/t.Fatal。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 测试文件:calc_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
got := add(1, 2)
if got != 3 {
t.Errorf("add(1,2) = %d; want 3", got)
}
}
// 表驱动测试:
func TestAddTable(t *testing.T) {
cases := []struct{ a, b, want int }{
{1, 2, 3}, {0, 0, 0}, {-1, 1, 0},
}
for _, c := range cases {
if got := add(c.a, c.b); got != c.want {
t.Errorf("add(%d,%d)=%d; want %d",
c.a, c.b, got, c.want)
}
}
}
// 运行:
// go test // 当前包
// go test ./... // 全部
// go test -v -run TestAdd
// 覆盖率:go test -cover

基准测试

go test -bench 基准测试。Benchmark 函数、b.N 次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "testing"
func BenchmarkConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
concat("a", "b") // 被测函数
}
}
// 运行:
// go test -bench=. -benchmem
// 输出:
// BenchmarkConcat-8 42984412
// 27.36 ns/op 16 B/op 2 allocs/op
// 选项:
// -bench=Concat 匹配名称
// -benchmem 显示内存分配
// -count=3 多次运行
// -benchtime=5s 固定时长
// 对比优化:
// 优化前后各跑一次比较
// 注意:
// 编译器可能内联/常量折叠
// 预热不明显可忽略

静态检查

go vet 检查常见错误。格式化错误、可疑构造。CI 必跑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 检查当前包:
// go vet
// 检查全部:
// go vet ./...
// 常见检出:
// 1. 格式串错误:
// fmt.Printf("%d", "x") 类型不匹配
// 2. 锁使用错误:
// 复制 sync.Mutex
// 3. 无用赋值
// 4. 字符串转义问题
// 5. 偏移量/索引越界
// 与其他工具:
// golangci-lint 聚合更多检查
// 静态分析:
// go build -gcflags=-m 看内联
// 最佳实践:
// 提交前必跑
// CI 集成 go vet ./...

格式化

gofmt 统一代码风格。goimports 管理导入。格式化即标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 格式化单个文件:
// gofmt -w file.go
// 格式化全部:
// gofmt -w .
// 检查不修改:
// gofmt -l .
// 递归目录:
// gofmt -l ./...
// 导入排序:
// goimports -w file.go
// 编辑器集成:
// 保存时自动格式化
// 团队协作:
// 统一格式无风格争论
// 注意:
// gofmt 只改格式不动逻辑
// CI 检查:
// test -z "$(gofmt -l .)"
// 混合制表符与空格自动修正

交叉编译

GOOS/GOARCH 跨平台编译。一次源码多平台二进制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 编译 Windows:
// GOOS=windows GOARCH=amd64 go build .
// 编译 Linux:
// GOOS=linux GOARCH=amd64 go build .
// 编译 macOS:
// GOOS=darwin GOARCH=arm64 go build .
// 32 位:
// GOOS=linux GOARCH=386
// ARM:
// GOOS=linux GOARCH=arm64
// 输出名带后缀:
// go build -o app.exe .
// 可用平台组合:
// go tool dist list
// 注意:
// CGO 默认关闭(纯 Go 才可交叉)
// 交叉编译无需目标系统环境
// 常见组合打发布矩阵

竞态检测

构建并启用竞态检测器。运行时报告数据竞争。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 运行带检测:
// go run -race main.go
// 测试带检测:
// go test -race ./...
// 构建带检测:
// go build -race
// 检测内容:
// 多个 goroutine 并发读写
// 同一内存地址
// 无同步保护
// 输出示例:
// WARNING: DATA RACE
// Read at 0x... by goroutine 5
// 修复方式:
// 加互斥锁 / 用 channel
// 用 atomic 操作
// 注意:
// 只检测运行时路径
// 覆盖率靠测试
// 生产二进制不要 -race
// CI 常规启用 -race

模块版本

go.mod 依赖版本管理。go get 更新、replace 替换、go.sum 校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 添加依赖:
// go get github.com/user/[email protected]
// 更新到最新:
// go get -u ./...
// 升级 major:
// go get module/v2@latest
// replace 本地调试:
// replace github.com/foo/bar => ../bar
// 或指向 fork:
// replace foo => github.com/user/foo v1.0.0
// 移除未用:
// go mod tidy
// 校验和:
// go mod verify
// 锁定版本:
// 看 go.mod require 块
// go.sum 保证一致性:
// 校验依赖哈希

CI 集成

持续集成常见步骤。测试、lint、构建、发布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 典型 CI 步骤:
// 1. 检出代码
// 2. 安装 Go:
// actions/setup-go
// 3. 缓存模块:
// $HOME/go/pkg/mod
// 4. 检查格式:
// gofmt -l .
// 5. 静态检查:
// go vet ./...
// 6. 测试:
// go test -race -cover ./...
// 7. 构建:
// go build ./...
// 8. 发布产物:
// 交叉编译多平台二进制
// 矩阵:
// go 版本 1.21 / 1.22
// OS linux/windows/darwin
// 门槛:
// 测试失败即失败
// 覆盖率阈值可选

官方链接

直达官方文档与资源。

关于本速查

本页是 Go 1.22 的自包含速查手册,覆盖语言核心与最常用标准库在真实项目中约 80% 的常见用法。内容偏向现代惯用法:短变量声明、结构化错误包装(errors.Is / errors.As)、泛型,以及 1.22 起的每次迭代独立循环变量。权威参考见官方 Go 教程与 Effective Go。 19 个章节各自聚焦一个主题——从第一个程序到 goroutine、接口与常见误区。每节拆成 8–14 个带示例的小节(每个 5–20 行),共约 150 个主题。代码片段刻意短小、自解释。 所有处理都在浏览器中完成——无上传、无追踪。本页是 GuruToolkit 免费开发者工具集的一部分;代码片段可自由使用,无任何担保。

版本 2.1.0