本工具使用的開源套件

本工具程式碼中捆綁了 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