跳到主要内容

Go 函数

函数是 Go 程序的基本组成单元。Go 的函数设计简洁而强大,支持多返回值、闭包、一等公民等特性。理解函数的工作原理和最佳实践,是编写高质量 Go 代码的基础。

函数基础

定义函数

func 函数名(参数列表) 返回值列表 {
// 函数体
return 返回值
}

函数由以下部分组成:

  • func 关键字:声明函数
  • 函数名:首字母大写表示可导出
  • 参数列表:可以有零个或多个参数
  • 返回值列表:可以有零个或多个返回值
  • 函数体:实际的代码逻辑

基本示例

// 无参数无返回值
func sayHello() {
fmt.Println("Hello!")
}

// 有参数无返回值
func greet(name string) {
fmt.Println("Hello,", name)
}

// 有参数有返回值
func add(a int, b int) int {
return a + b
}

// 相同类型参数可以合并
func addCompact(a, b int) int {
return a + b
}

// 调用
sayHello()
greet("张三")
result := add(1, 2)
fmt.Println(result) // 3

函数声明规则

// 规则一:相邻的同类型参数可以合并
func process(a, b int, c string) {}

// 规则二:不同类型必须分开声明
func mixed(a int, b string, c float64) {}

// 规则三:参数名和类型不能省略(除了合并的情况)
// func broken(int, string) {} // 编译错误

// 规则四:不接收参数的函数,参数列表为空
func noParams() {}

// 规则五:不返回值的函数,返回值列表可以省略
func noReturn() {
// 隐式返回
}

参数传递机制

理解 Go 的参数传递机制对于编写正确的程序至关重要。Go 中所有参数传递都是值传递,但这个"值"的含义需要仔细理解。

值传递的本质

当调用函数时,参数会被复制一份传递给函数。这意味着函数内部对参数的修改不会影响原始值:

func modifyValue(x int) {
x = 100 // 修改的是副本
fmt.Println("函数内:", x) // 100
}

func main() {
a := 10
modifyValue(a)
fmt.Println("函数外:", a) // 10,原值不变
}

指针传递

如果需要在函数内部修改原始值,需要传递指针:

func modifyPointer(x *int) {
*x = 100 // 通过指针修改原始值
}

func main() {
a := 10
modifyPointer(&a)
fmt.Println(a) // 100,原值被修改
}

指针传递仍然是值传递:传递的是指针的副本,但两个指针指向同一个地址。

切片和映射的传递

切片和映射本身是引用类型,但传递时仍然发生值复制——复制的是切片头或映射头的副本:

func modifySlice(s []int) {
s[0] = 100 // 可以修改底层数组
s = append(s, 4) // 修改的是切片头副本,不影响原切片
}

func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // [100, 2, 3],第一个元素被修改,但长度不变
}

理解要点

// 切片头结构(伪代码)
type slice struct {
ptr unsafe.Pointer // 底层数组指针
len int // 长度
cap int // 容量
}

传递切片时,复制的是这个 slice 结构。因此:

  • 可以通过 ptr 修改底层数组的元素
  • 但修改 len 或 cap 不会影响原切片

如果需要函数能够修改切片的长度,需要传递切片的指针:

func modifySlicePtr(s *[]int) {
*s = append(*s, 4)
}

func main() {
s := []int{1, 2, 3}
modifySlicePtr(&s)
fmt.Println(s) // [1, 2, 3, 4]
}

传递大结构体

对于大型结构体,传递指针可以避免复制开销:

type LargeStruct struct {
data [10000]int
}

// 不推荐:复制整个结构体
func processValue(v LargeStruct) {}

// 推荐:只传递指针(8字节)
func processPointer(v *LargeStruct) {}

// 推荐:对于只读操作,也可以传递指针
func readOnly(v *LargeStruct) {
// 只读取,不修改
}

最佳实践

  • 小结构体(如 Point{X, Y int}):值传递
  • 大结构体:指针传递
  • 需要修改原值:指针传递
  • 只读操作:视情况选择(一致性原则)

返回值

单返回值

func add(a, b int) int {
return a + b
}

多返回值

Go 的多返回值是其特色之一,常用于返回结果和错误:

// 返回商和余数
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

// 调用
q, r := divide(10, 3)
fmt.Println(q, r) // 3 1

// 忽略某个返回值
q, _ := divide(10, 3)

// 忽略所有返回值(不推荐)
divide(10, 3)

命名返回值

可以为返回值命名,命名后会在函数开始时自动初始化为零值:

func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 裸返回,自动返回 x 和 y
}

// 等价于
func splitExplicit(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return x, y
}

命名返回值的优缺点

优点:

  • 代码更清晰,明确每个返回值的含义
  • 可以简化代码(裸返回)
  • 文档生成时更友好

缺点:

  • 裸返回可能降低可读性
  • 容易造成变量遮蔽

建议:在简短函数中使用命名返回值,复杂函数显式返回更清晰。

返回错误

Go 的惯用方式是通过返回 error 来处理错误:

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)

错误处理的最佳实践

// 好的实践:正常路径在 if 外面
result, err := someFunction()
if err != nil {
return err
}
// 继续处理 result

// 不推荐:正常路径嵌套在 else 中
if result, err := someFunction(); err == nil {
// 处理 result
} else {
return err
}

不定参数

不定参数允许函数接受零个或多个同类型参数。

基本语法

// 语法:...类型
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}

// 调用方式
fmt.Println(sum()) // 0
fmt.Println(sum(1)) // 1
fmt.Println(sum(1, 2)) // 3
fmt.Println(sum(1, 2, 3)) // 6

不定参数的本质

不定参数在函数内部是切片类型:

func inspect(nums ...int) {
fmt.Printf("类型: %T\n", nums) // []int
fmt.Printf("长度: %d\n", len(nums))
fmt.Printf("值: %v\n", nums)
}

切片展开

可以使用 ... 操作符将切片展开传递给不定参数函数:

nums := []int{1, 2, 3, 4, 5}
result := sum(nums...) // 展开切片
fmt.Println(result) // 15

不定参数的位置

不定参数必须是函数的最后一个参数:

// 正确:不定参数在最后
func format(prefix string, values ...int) string {
return fmt.Sprintf("%s: %v", prefix, values)
}

// 错误:不定参数不在最后
// func invalid(values ...int, suffix string) {}

不定参数与空接口

结合空接口可以实现接受任意类型的参数:

func printAll(values ...interface{}) {
for _, v := range values {
fmt.Printf("%v (%T)\n", v, v)
}
}

printAll(1, "hello", 3.14, true)
// 1 (int)
// hello (string)
// 3.14 (float64)
// true (bool)

函数类型

在 Go 中,函数是一等公民,这意味着函数可以像其他值一样被传递、赋值和操作。

函数类型声明

// 声明函数类型
type Operation func(int, int) int

// 使用函数类型
func calculate(a, b int, op Operation) int {
return op(a, b)
}

func add(a, b int) int { return a + b }
func sub(a, b int) int { return a - b }

func main() {
// 将函数赋值给变量
var op Operation = add
fmt.Println(op(10, 5)) // 15

// 作为参数传递
fmt.Println(calculate(10, 5, add)) // 15
fmt.Println(calculate(10, 5, sub)) // 5
}

函数作为参数

// 接受函数作为参数
func apply(fn func(int, int) int, a, b int) int {
return fn(a, b)
}

func main() {
// 传递匿名函数
result := apply(func(a, b int) int {
return a * b
}, 10, 20)
fmt.Println(result) // 200

// 传递命名函数
result = apply(add, 10, 20)
fmt.Println(result) // 30
}

函数作为返回值

// 返回函数
func getOperation(name string) func(int, int) int {
switch name {
case "add":
return func(a, b int) int { return a + b }
case "sub":
return func(a, b int) int { return a - b }
case "mul":
return func(a, b int) int { return a * b }
default:
return func(a, b int) int { return 0 }
}
}

func main() {
add := getOperation("add")
fmt.Println(add(10, 5)) // 15

mul := getOperation("mul")
fmt.Println(mul(10, 5)) // 50
}

函数类型的方法

函数类型可以定义方法,这在创建中间件等场景非常有用:

type Middleware func(http.Handler) http.Handler

// 链式调用中间件
func (m Middleware) Then(next http.Handler) http.Handler {
return m(next)
}

func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}

func main() {
var m Middleware = loggingMiddleware
handler := m.Then(http.DefaultServeMux)
http.ListenAndServe(":8080", handler)
}

闭包

闭包是指一个函数可以访问并操作其外部作用域的变量。闭包是 Go 中实现函数式编程的重要工具。

闭包的基本概念

func counter() func() int {
count := 0 // 外部变量
return func() int {
count++ // 访问并修改外部变量
return count
}
}

func main() {
c1 := counter()
c2 := counter()

// 每个闭包有自己独立的变量环境
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c1()) // 3

fmt.Println(c2()) // 1
fmt.Println(c2()) // 2
}

闭包捕获变量

闭包捕获的是变量的引用,而不是值:

func main() {
var funcs []func()

for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 捕获的是变量 i 的引用
})
}

// 所有闭包都打印 3(循环结束后的值)
for _, f := range funcs {
f()
}
}
// 输出: 3, 3, 3

Go 1.22 前后的行为差异

Go 1.22 对 for 循环变量作用域进行了重大修改,解决了这个长期存在的问题:

Go 1.21 及之前:循环变量在整个循环中只创建一次,每次迭代更新同一个变量。

// Go 1.21 及之前:所有闭包捕获同一个变量
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 捕获的是同一个变量 i
}()
}
// 可能输出: 3, 3, 3(或 2, 3, 3 等不确定结果)

Go 1.22 及之后:每次迭代都创建新的变量实例,闭包捕获的是各自迭代的值。

// Go 1.22+:每次迭代创建新变量,闭包捕获各自迭代值
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 每个 goroutine 捕获不同的 i
}()
}
// 输出: 0, 1, 2(顺序可能不同)

官方文档说明

根据 Go 官方博客《Fixing For Loops in Go 1.22》,这个变更的影响是:

  • 对于 for range 循环,效果相当于每次迭代开始时执行 v := v
  • 对于三段式 for 循环,效果相当于每次迭代开始时执行 i := i

兼容性控制

新语义只应用于 go.mod 中声明 go 1.22 或更高版本的模块。这确保了:

  • 旧代码行为不变
  • 开发者可以逐步迁移
  • 向前兼容性得到保证

预览模式(Go 1.21)

在 Go 1.21 中可以预览这个变更:

GOEXPERIMENT=loopvar go test

Go 1.21 及之前的解决方案

如果需要兼容 Go 1.21 及更早版本,仍应使用以下方式:

// 方式一:创建局部变量(推荐)
func main() {
var funcs []func()

for i := 0; i < 3; i++ {
i := i // 创建新变量,捕获当前值
funcs = append(funcs, func() {
fmt.Println(i)
})
}

for _, f := range funcs {
f()
}
}
// 输出: 0, 1, 2

// 方式二:将值作为参数传递
func main() {
var funcs []func()

for i := 0; i < 3; i++ {
funcs = append(funcs, func(n int) {
fmt.Println(n)
})
}

for i, f := range funcs {
f(i)
}
}

测试中的常见陷阱

Go 1.22 的变更暴露了许多错误的测试代码:

// 错误的测试:Go 1.21 中会错误地通过
func TestAllEvenBuggy(t *testing.T) {
testCases := []int{1, 2, 4, 6}
for _, v := range testCases {
t.Run("sub", func(t *testing.T) {
t.Parallel() // 并行执行
if v&1 != 0 {
t.Fatal("odd v", v)
}
})
}
}

// Go 1.21 中:测试通过(错误!因为所有子测试都检查 v=6)
// Go 1.22+ 中:测试失败(正确!会检测到 1 是奇数)

最佳实践:在 Go 1.22+ 中,可以放心地在并行测试中使用循环变量。对于旧版本,需要在 t.Parallel() 之前捕获变量。

闭包的实际应用

工厂函数

func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}

func main() {
addJpg := makeSuffix(".jpg")
addTxt := makeSuffix(".txt")

fmt.Println(addJpg("photo")) // photo.jpg
fmt.Println(addJpg("photo.jpg")) // photo.jpg
fmt.Println(addTxt("doc")) // doc.txt
}

状态保持

func makeAccumulator(initial int) func(int) int {
sum := initial
return func(n int) int {
sum += n
return sum
}
}

func main() {
acc := makeAccumulator(10)
fmt.Println(acc(5)) // 15
fmt.Println(acc(10)) // 25
fmt.Println(acc(3)) // 28
}

延迟计算

func lazyValue(fn func() int) func() int {
var value int
var computed bool
return func() int {
if !computed {
value = fn()
computed = true
}
return value
}
}

func main() {
expensive := lazyValue(func() int {
fmt.Println("计算中...")
time.Sleep(time.Second)
return 42
})

fmt.Println("准备获取值")
fmt.Println(expensive()) // 第一次调用会计算
fmt.Println(expensive()) // 后续调用返回缓存值
}

defer

defer 语句将函数调用推迟到外层函数返回之后执行。它是 Go 中管理资源的核心机制。

基本用法

func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // 确保文件关闭

// 处理文件...
return nil
}

defer 的执行时机

defer 在函数返回前执行,但在返回值被计算之后:

func deferReturn() (result int) {
defer func() {
fmt.Println("defer 执行, result =", result)
}()

result = 42
fmt.Println("函数体执行完成")
return result
}

func main() {
fmt.Println("返回值:", deferReturn())
}
// 输出:
// 函数体执行完成
// defer 执行, result = 42
// 返回值: 42

defer 与返回值

这是一个容易出错的点:

func f1() int {
i := 0
defer func() {
i++ // 修改的是局部变量 i
}()
return i // 返回 0
}

func f2() (i int) {
defer func() {
i++ // 修改的是命名返回值
}()
return i // 返回 1
}

func f3() (i int) {
defer func() {
i++ // defer 可以修改返回值
}()
return 0 // 返回 1(defer 后执行)
}

多个 defer 的执行顺序

多个 defer 按 LIFO(后进先出)顺序执行:

func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("函数体")
}
// 输出:
// 函数体
// 3
// 2
// 1

defer 参数求值

defer 的参数在声明时就已求值:

func main() {
i := 0
defer fmt.Println("defer:", i) // 此时 i = 0
i++
fmt.Println("main:", i)
}
// 输出:
// main: 1
// defer: 0

如果需要在执行时才求值:

func main() {
i := 0
defer func() {
fmt.Println("defer:", i) // 执行时读取 i
}()
i++
fmt.Println("main:", i)
}
// 输出:
// main: 1
// defer: 1

defer 常见用途

资源清理

func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

// 处理文件...
return nil
}

互斥锁释放

var mu sync.Mutex
var counter int

func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}

追踪函数执行时间

func trackTime(start time.Time, name string) {
elapsed := time.Since(start)
log.Printf("%s 执行耗时: %v", name, elapsed)
}

func slowOperation() {
defer trackTime(time.Now(), "slowOperation")
time.Sleep(time.Second)
}

恢复 panic

func safeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()

// 可能 panic 的操作
riskyOperation()
return nil
}

defer 的性能考虑

defer 有少量性能开销,但在大多数情况下可以忽略。对于极高性能要求的代码,可以考虑手动管理:

// 高性能场景:避免 defer
func highPerformance() {
mu.Lock()
// 关键代码
mu.Unlock() // 手动释放
}

// 正常场景:使用 defer
func normalCase() {
mu.Lock()
defer mu.Unlock() // 推荐,更安全
// 代码
}

函数式选项模式

函数式选项模式是 Go 中配置复杂对象的惯用方法,广泛应用于标准库和第三方库中。

问题:多种配置方式

假设我们需要创建一个服务器,有很多配置选项:

type Server struct {
host string
port int
timeout time.Duration
maxConns int
tls bool
certFile string
keyFile string
}

// 方式一:多参数构造函数(不好)
func NewServer(host string, port int, timeout time.Duration,
maxConns int, tls bool, certFile, keyFile string) *Server {
// 参数太多,容易出错
}

// 方式二:配置结构体(部分解决)
type ServerConfig struct {
Host string
Port int
Timeout time.Duration
MaxConns int
TLS bool
CertFile string
KeyFile string
}

func NewServerWithConfig(cfg ServerConfig) *Server {
// 但默认值处理麻烦
}

解决方案:函数式选项模式

// 定义选项函数类型
type Option func(*Server)

// 定义选项函数
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}

func WithTimeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}

func WithTLS(certFile, keyFile string) Option {
return func(s *Server) {
s.tls = true
s.certFile = certFile
s.keyFile = keyFile
}
}

func WithMaxConnections(max int) Option {
return func(s *Server) {
s.maxConns = max
}
}

// 构造函数
func NewServer(host string, opts ...Option) *Server {
// 设置默认值
server := &Server{
host: host,
port: 8080, // 默认端口
timeout: 30 * time.Second, // 默认超时
maxConns: 100, // 默认最大连接数
}

// 应用选项
for _, opt := range opts {
opt(server)
}

return server
}

// 使用
func main() {
// 只使用默认值
s1 := NewServer("localhost")

// 自定义部分选项
s2 := NewServer("localhost",
WithPort(9000),
WithTimeout(60*time.Second),
)

// 启用 TLS
s3 := NewServer("localhost",
WithPort(443),
WithTLS("cert.pem", "key.pem"),
WithMaxConnections(1000),
)
}

模式的优点

  1. API 清晰:选项名有明确的语义
  2. 向后兼容:添加新选项不影响现有代码
  3. 默认值明确:在构造函数中统一设置
  4. 灵活组合:可以任意组合选项
  5. 可扩展:添加新选项非常简单

验证选项

func WithPort(port int) Option {
return func(s *Server) {
if port < 0 || port > 65535 {
panic("invalid port")
}
s.port = port
}
}

// 或者返回错误
type Option func(*Server) error

func WithPort(port int) Option {
return func(s *Server) error {
if port < 0 || port > 65535 {
return errors.New("invalid port")
}
s.port = port
return nil
}
}

func NewServer(host string, opts ...Option) (*Server, error) {
server := &Server{host: host, port: 8080}
for _, opt := range opts {
if err := opt(server); err != nil {
return nil, err
}
}
return server, nil
}

中间件模式

中间件是一种装饰器模式的应用,常用于 HTTP 服务中对请求进行预处理和后处理。

基本中间件

// 中间件类型
type Middleware func(http.Handler) http.Handler

// 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

// 调用下一个处理器
next.ServeHTTP(w, r)

log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}

// 恢复中间件(捕获 panic)
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %v", err)
http.Error(w, "Internal Server Error", 500)
}
}()
next.ServeHTTP(w, r)
})
}

// 使用
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})

// 包装中间件
handler := RecoveryMiddleware(LoggingMiddleware(mux))

http.ListenAndServe(":8080", handler)
}

中间件链

// 创建中间件链
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)

// 应用中间件链
handler := Chain(mux,
LoggingMiddleware,
RecoveryMiddleware,
AuthMiddleware,
)

http.ListenAndServe(":8080", handler)
}

认证中间件

func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

// 验证 token
userID, err := validateToken(token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}

// 将用户信息存入上下文
ctx := context.WithValue(r.Context(), "userID", userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

CORS 中间件

func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}

next.ServeHTTP(w, r)
})
}

高阶函数

高阶函数是指接受函数作为参数或返回函数的函数。Go 中有很多使用高阶函数的场景。

切片操作

// 过滤
func Filter[T any](slice []T, predicate func(T) bool) []T {
result := make([]T, 0)
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}

// 映射
func Map[T, U any](slice []T, transform func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = transform(v)
}
return result
}

// 归约
func Reduce[T, U any](slice []T, initial U, reducer func(U, T) U) U {
result := initial
for _, v := range slice {
result = reducer(result, v)
}
return result
}

// 使用
func main() {
nums := []int{1, 2, 3, 4, 5}

// 过滤偶数
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2, 4]

// 平方
squares := Map(nums, func(n int) int { return n * n })
fmt.Println(squares) // [1, 4, 9, 16, 25]

// 求和
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println(sum) // 15
}

函数组合

// 组合两个函数
func Compose[A, B, C any](f func(B) C, g func(A) B) func(A) C {
return func(a A) C {
return f(g(a))
}
}

// 使用
func main() {
addOne := func(x int) int { return x + 1 }
double := func(x int) int { return x * 2 }

// 先加一,再翻倍
addThenDouble := Compose(double, addOne)
fmt.Println(addThenDouble(3)) // 8

// 先翻倍,再加一
doubleThenAdd := Compose(addOne, double)
fmt.Println(doubleThenAdd(3)) // 7
}

柯里化

// 柯里化:将多参数函数转换为单参数函数链
func Curry[A, B, C any](f func(A, B) C) func(A) func(B) C {
return func(a A) func(B) C {
return func(b B) C {
return f(a, b)
}
}
}

func main() {
add := func(a, b int) int { return a + b }

curriedAdd := Curry(add)
addFive := curriedAdd(5) // 固定第一个参数

fmt.Println(addFive(3)) // 8
fmt.Println(addFive(10)) // 15
}

偏函数应用

// 偏应用:固定部分参数
func Partial[A, B, C any](f func(A, B) C, a A) func(B) C {
return func(b B) C {
return f(a, b)
}
}

func main() {
multiply := func(a, b int) int { return a * b }

double := Partial(multiply, 2)
triple := Partial(multiply, 3)

fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}

迭代器函数(Go 1.23+)

Go 1.23 引入了迭代器(Range Over Func)特性,允许 for range 直接遍历函数。这是一种强大的函数式编程模式,使得自定义集合的遍历变得简洁优雅。

迭代器函数的类型定义

Go 1.23 在标准库中定义了两种迭代器函数类型:

// 单值迭代器
type Seq[V any] func(yield func(V) bool)

// 键值对迭代器
type Seq2[K, V any] func(yield func(K, V) bool)

核心概念:迭代器函数接受一个 yield 回调函数作为参数。每次调用 yield 会产生一个迭代值,如果 yield 返回 false,表示调用者希望停止迭代。

基本迭代器示例

package main

import "fmt"

// 定义一个简单的整数序列迭代器
func Ints(n int) func(func(int) bool) {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return // 如果 yield 返回 false,停止迭代
}
}
}
}

func main() {
// 使用 for range 遍历迭代器
for v := range Ints(5) {
fmt.Println(v)
}
// 输出: 0, 1, 2, 3, 4
}

执行机制说明

迭代器的执行是一种"推"模式:

  1. for range 调用迭代器函数
  2. 迭代器函数执行,通过 yield "推送"值给循环体
  3. 循环体处理值,返回是否继续
  4. 如果继续,迭代器继续执行;否则迭代器提前返回

迭代器的实际应用

遍历自定义集合

package main

import "fmt"

// 自定义集合类型
type IntSet struct {
values map[int]bool
}

func NewIntSet(vals ...int) *IntSet {
s := &IntSet{values: make(map[int]bool)}
for _, v := range vals {
s.values[v] = true
}
return s
}

// 定义迭代器方法
func (s *IntSet) All() func(func(int) bool) {
return func(yield func(int) bool) {
for v := range s.values {
if !yield(v) {
return
}
}
}
}

func main() {
set := NewIntSet(1, 2, 3, 4, 5)

// 使用 for range 遍历自定义集合
for v := range set.All() {
fmt.Println(v)
}
}

生成器模式

package main

import (
"fmt"
"strings"
)

// 单词生成器
func Words(text string) func(func(string) bool) {
return func(yield func(string) bool) {
for _, word := range strings.Fields(text) {
if !yield(word) {
return
}
}
}
}

func main() {
text := "Hello, Go 1.23 迭代器示例"

for word := range Words(text) {
fmt.Println(word)
}
}

无限序列

package main

import "fmt"

// 无限自然数序列
func Naturals() func(func(int) bool) {
return func(yield func(int) bool) {
for i := 0; ; i++ {
if !yield(i) {
return
}
}
}
}

func main() {
// 打印前 10 个自然数
count := 0
for n := range Naturals() {
fmt.Println(n)
count++
if count >= 10 {
break // 可以安全地提前退出
}
}
}

提前终止迭代

for range 循环提前退出(如 breakreturn),迭代器会被正确通知:

func main() {
for v := range Ints(100) {
fmt.Println(v)
if v == 3 {
break // 提前终止
}
}
// 迭代器函数会收到 yield 返回 false,执行清理逻辑
}

组合迭代器

迭代器可以方便地组合,实现链式处理:

package main

import "fmt"

// 过滤迭代器
func Filter[T any](seq func(func(T) bool), predicate func(T) bool) func(func(T) bool) {
return func(yield func(T) bool) {
for v := range seq {
if predicate(v) {
if !yield(v) {
return
}
}
}
}
}

// 映射迭代器
func Map[T, U any](seq func(func(T) bool), transform func(T) U) func(func(U) bool) {
return func(yield func(U) bool) {
for v := range seq {
if !yield(transform(v)) {
return
}
}
}
}

func main() {
// 链式操作:生成 -> 过滤 -> 映射
for v := range Map(
Filter(Ints(20), func(n int) bool { return n%2 == 0 }),
func(n int) int { return n * n },
) {
fmt.Println(v) // 0, 4, 16, 36, 64, ...
}
}

Pull 迭代器

除了"推"模式,Go 还提供了"拉"模式迭代器,用于需要手动控制迭代的场景:

package main

import (
"fmt"
"iter"
"slices"
)

func main() {
numbers := []int{1, 2, 3, 4, 5}

// 将推迭代器转换为拉迭代器
next, stop := iter.Pull(slices.Values(numbers))
defer stop() // 确保释放资源

for {
v, ok := next()
if !ok {
break
}
fmt.Println(v)
}
}

Pull 迭代器的适用场景

  • 需要在迭代过程中做复杂的状态管理
  • 需要跨多个函数传递迭代状态
  • 与其他语言的迭代器模式对接
  • 实现复杂的解析逻辑

标准库中的迭代器支持

Go 1.23 在标准库中广泛添加了迭代器支持:

package main

import (
"fmt"
"maps"
"slices"
)

func main() {
// slices 包的迭代器
nums := []int{1, 2, 3, 4, 5}

for v := range slices.Values(nums) {
fmt.Println(v)
}

for i, v := range slices.All(nums) {
fmt.Printf("索引 %d: %d\n", i, v)
}

// maps 包的迭代器
m := map[string]int{"a": 1, "b": 2}

for k := range maps.Keys(m) {
fmt.Println(k)
}

for v := range maps.Values(m) {
fmt.Println(v)
}

for k, v := range maps.All(m) {
fmt.Printf("%s: %d\n", k, v)
}
}

迭代器的最佳实践

  1. 命名约定:迭代器方法通常命名为 All()Values()Keys()
  2. 资源管理:如果迭代器持有资源,确保在提前终止时正确释放
  3. 避免副作用:迭代器应该是纯函数,避免修改被迭代的集合
  4. 文档说明:明确说明迭代器是否保证顺序
// 好的实践:带资源管理的迭代器
func (f *File) Lines() func(func(string) bool) {
return func(yield func(string) bool) {
scanner := bufio.NewScanner(f)
defer f.Close() // 确保资源释放
for scanner.Scan() {
if !yield(scanner.Text()) {
return
}
}
}
}

迭代器与闭包的关系

迭代器本质上是闭包的高级应用——它捕获了外部状态(如集合、计数器),并通过 yield 函数与调用者交互。理解闭包是理解迭代器的基础。

init 和 main 函数

main 函数

main 函数是程序的入口点:

package main

import "fmt"

func main() {
fmt.Println("程序开始")
// 程序逻辑
fmt.Println("程序结束")
}

特点:

  • 必须在 main 包中
  • 不接受参数,不返回值
  • 程序从这里开始执行
  • main 函数结束,程序结束

init 函数

init 函数在包初始化时自动执行:

package main

import "fmt"

var name string

func init() {
name = "张三"
fmt.Println("init 函数执行")
}

func main() {
fmt.Println("main 函数执行:", name)
}

// 输出:
// init 函数执行
// main 函数执行: 张三

特点:

  • 每个包可以有多个 init 函数
  • 每个源文件可以有多个 init 函数
  • 不能被调用,自动执行
  • 按照导入依赖顺序执行

执行顺序

导入包 -> 常量初始化 -> 变量初始化 -> init() -> main()
// 文件 a.go
package main

import "fmt"

var a = initA()

func initA() int {
fmt.Println("初始化变量 a")
return 1
}

func init() {
fmt.Println("init a.go")
}

// 文件 b.go
package main

import "fmt"

var b = initB()

func initB() int {
fmt.Println("初始化变量 b")
return 2
}

func init() {
fmt.Println("init b.go")
}

func main() {
fmt.Println("main")
}

// 可能的输出(顺序可能变化):
// 初始化变量 a
// 初始化变量 b
// init a.go
// init b.go
// main

init 的常见用途

// 注册数据库驱动
package mysql

import "database/sql"

func init() {
sql.Register("mysql", &MySQLDriver{})
}

// 配置初始化
package config

var Config *Configuration

func init() {
var err error
Config, err = LoadConfig("config.yaml")
if err != nil {
panic(err)
}
}

// 验证前置条件
package main

func init() {
if os.Getenv("API_KEY") == "" {
panic("API_KEY environment variable is required")
}
}

panic 和 recover

panicrecover 是 Go 的异常处理机制,但应该谨慎使用。

panic

panic 会立即停止当前函数的执行,并开始展开调用栈:

func mustPositive(n int) {
if n < 0 {
panic(fmt.Sprintf("负数不被允许: %d", n))
}
fmt.Println("数字:", n)
}

func main() {
mustPositive(10) // 正常
mustPositive(-5) // panic
fmt.Println("不会执行")
}

适用场景

  • 程序遇到无法恢复的错误
  • 初始化失败
  • 不可能发生的情况(防御性编程)

recover

recover 用于捕获 panic,只能在 defer 中使用:

func safeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()

mightPanic()
return nil
}

func mightPanic() {
panic("something went wrong")
}

func main() {
err := safeOperation()
if err != nil {
fmt.Println("捕获到错误:", err)
}
fmt.Println("程序继续执行")
}

HTTP 服务器中的 recover

func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", 500)
}
}()
next.ServeHTTP(w, r)
})
}

何时使用 panic

应该使用 panic

  • 程序初始化失败
  • 达到不可能的代码路径
  • 关键资源不可用

不应该使用 panic

  • 文件不存在
  • 网络连接失败
  • 用户输入错误
  • 配置错误

递归

递归是函数调用自身的编程技术。

基本递归

// 阶乘
func factorial(n int) int {
if n <= 1 {
return 1 // 终止条件
}
return n * factorial(n-1)
}

// 斐波那契数列
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}

尾递归

Go 不自动优化尾递归,但可以手动转换为迭代:

// 尾递归形式(Go 不优化)
func factorialTail(n, acc int) int {
if n <= 1 {
return acc
}
return factorialTail(n-1, n*acc)
}

// 迭代形式(推荐)
func factorialIterative(n int) int {
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result
}

递归的实际应用

遍历树结构

type TreeNode struct {
Value int
Children []*TreeNode
}

func (n *TreeNode) Traverse(fn func(*TreeNode)) {
fn(n) // 处理当前节点
for _, child := range n.Children {
child.Traverse(fn) // 递归处理子节点
}
}

深度优先搜索

func DFS(graph map[int][]int, start int, visited map[int]bool) {
visited[start] = true
fmt.Println("访问:", start)

for _, neighbor := range graph[start] {
if !visited[neighbor] {
DFS(graph, neighbor, visited)
}
}
}

最佳实践

函数命名

// 好的命名:动词开头,描述清晰
func CalculateTotalPrice(items []Item) float64
func SendEmail(to, subject, body string) error
func GetUserByID(id int) (*User, error)

// 不好的命名
func Total() float64 // 不清楚是什么的总计
func Email() error // 发送还是获取?
func Process() error // 处理什么?

命名约定

函数类型命名模式示例
获取值Get* 或直接用名词GetName(), User.Name()
设置值Set*SetName(name string)
判断布尔Is*, Has*, Can*IsValid(), HasPermission()
转换To*ToString(), ToJSON()
创建New*NewUser(), NewConfig()
验证Validate*Check*ValidateEmail(), CheckPermission()

函数长度

  • 理想情况:函数应该足够短,一目了然
  • 经验法则:一个函数不应该超过一屏(约 50 行)
  • 如果函数太长,考虑拆分成多个小函数

单一职责

// 不好:函数做了太多事情
func ProcessOrder(order *Order) error {
// 验证订单
// 检查库存
// 计算价格
// 创建支付
// 发送邮件
// 更新数据库
}

// 好:每个函数只做一件事
func ProcessOrder(order *Order) error {
if err := ValidateOrder(order); err != nil {
return err
}
if err := CheckInventory(order); err != nil {
return err
}
if err := CalculatePrice(order); err != nil {
return err
}
// ...
}

错误处理

// 好:立即处理错误
result, err := someFunction()
if err != nil {
return fmt.Errorf("操作失败: %w", err)
}

// 不好:忽略错误
result, _ := someFunction()

// 不好:错误处理嵌套太深
if result, err := someFunction(); err == nil {
if result2, err := anotherFunction(result); err == nil {
// 继续嵌套...
} else {
return err
}
} else {
return err
}

参数设计

参数数量

  • 理想情况:0-3 个参数
  • 如果需要更多参数,考虑使用结构体或选项模式
// 不好:参数太多
func CreateUser(name string, age int, email string, phone string,
address string, city string, country string) *User

// 好:使用配置结构体
type UserConfig struct {
Name string
Age int
Email string
Phone string
Address string
}

func CreateUser(cfg UserConfig) *User

// 好:使用函数式选项模式
func NewUser(name string, opts ...UserOption) *User

避免布尔参数

// 不好:布尔参数含义不清
func Process(data []byte, async bool)

// 调用时看不懂含义
Process(data, true) // true 是什么意思?

// 好:使用明确的方式
func ProcessSync(data []byte)
func ProcessAsync(data []byte)

// 或者使用选项
func Process(data []byte, opts ...ProcessOption)
Process(data, WithAsync())

文档注释

// Add 返回两个整数的和。
// 参数 a 和 b 应该是有效的整数。
// 如果结果溢出,行为未定义。
func Add(a, b int) int {
return a + b
}

// 更完整的注释格式
// CreateUser 创建一个新的用户实例。
//
// 参数:
// - name: 用户名,不能为空
// - age: 年龄,必须为非负数
//
// 返回:
// - *User: 创建的用户实例
// - error: 如果参数无效,返回错误
//
// 示例:
//
// user, err := CreateUser("张三", 25)
// if err != nil {
// log.Fatal(err)
// }
func CreateUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name cannot be empty")
}
if age < 0 {
return nil, errors.New("age cannot be negative")
}
return &User{Name: name, Age: age}, nil
}

避免副作用

// 不好:函数有副作用
var counter int

func GetID() int {
counter++ // 副作用:修改了全局状态
return counter
}

// 好:函数是纯函数,没有副作用
func NextID(current int) int {
return current + 1
}

// 或者使用结构体封装状态
type IDGenerator struct {
counter int
}

func (g *IDGenerator) Next() int {
g.counter++
return g.counter
}

上下文传递

对于长时间运行或需要取消的操作,应该接受 context.Context 作为第一个参数:

// 好:支持取消和超时
func FetchData(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
// ...
}

// 调用示例
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data, err := FetchData(ctx, "https://example.com")

优雅的错误处理模式

// 使用 defer 简化错误处理
func ProcessFile(path string) (err error) {
file, err := os.Open(path)
if err != nil {
return err
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
if err == nil {
err = closeErr // 只有在没有其他错误时才返回关闭错误
}
}
}()

// 处理文件...
return nil
}

函数作为参数的设计

当函数作为参数时,定义清晰的函数类型:

// 定义清晰的函数类型
type CompareFunc[T any] func(a, b T) int
type FilterFunc[T any] func(T) bool
type MapFunc[T, U any] func(T) U

// 使用
func Sort[T any](slice []T, compare CompareFunc[T]) {
// ...
}

func Filter[T any](slice []T, filter FilterFunc[T]) []T {
// ...
}

小结

本章详细介绍了 Go 函数的核心概念和高级用法:

  1. 函数基础:定义、参数、返回值
  2. 参数传递:理解值传递的本质
  3. 多返回值:Go 的特色功能
  4. 不定参数:灵活的参数处理
  5. 函数类型:函数作为一等公民
  6. 闭包:捕获外部变量,注意 Go 1.22 前后的行为差异
  7. defer:延迟执行和资源管理
  8. 函数式选项模式:配置复杂对象
  9. 中间件模式:装饰器的应用
  10. 高阶函数:函数组合和变换
  11. 迭代器函数(Go 1.23+):使用 for range 遍历函数
  12. panic/recover:异常处理
  13. 递归:函数调用自身

练习

  1. 基础练习:实现一个支持链式调用的计算器函数
  2. 闭包练习:使用闭包实现一个令牌桶限流器
  3. 高阶函数练习:实现一个通用的重试函数,支持自定义重试次数和延迟
  4. 选项模式练习:使用函数式选项模式重构一个复杂的配置函数
  5. 中间件练习:编写一个中间件链,包含日志、认证、恢复功能
  6. 函数组合练习:实现一个高阶函数 Pipe,支持多个函数的管道式组合
  7. 迭代器练习(Go 1.23+):实现一个树的深度优先遍历迭代器
  8. 错误处理练习:实现一个数据库事务包装函数,正确处理各种错误场景

参考资源