Go 的函数设计、方法接收者、闭包与高阶函数


一、函数基础

1.1 函数定义

// 基本语法
func 函数名(参数列表) 返回类型 {
    // 函数体
}

// 多参数
func add(a int, b int) int {
    return a + b
}

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

// 多返回值(Go 的核心特性之一)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// 使用多返回值
result, err := divide(10, 3)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result)  // 3.3333333333333335

1.2 命名返回值

// 返回值可以有名字,相当于在函数内部声明了局部变量
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return  // 裸 return,自动返回 x, y
}

// 命名返回值 + 裸 return 在短函数中可读性好
// 但长函数中容易出错,建议谨慎使用

1.3 可变参数函数(Variadic)

// 使用 ... 实现可变参数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

fmt.Println(sum(1, 2, 3))       // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15

// 传递切片给可变参数:使用 ...
nums := []int{1, 2, 3}
fmt.Println(sum(nums...))  // 6

二、函数作为值

2.1 函数是一等公民

// 函数可以赋值给变量
add := func(a, b int) int {
    return a + b
}
fmt.Println(add(1, 2))  // 3

// 函数可以作为参数传递
func apply(a, b int, op func(int, int) int) int {
    return op(a, b)
}

result := apply(10, 5, func(a, b int) int { return a - b })
fmt.Println(result)  // 5

// 函数可以作为返回值
func makeMultiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}

double := makeMultiplier(2)
fmt.Println(double(5))  // 10

三、闭包(Closure)

闭包是引用了外部变量的匿名函数。Go 中每次创建闭包都会捕获变量的引用。

3.1 基本闭包

// 计数器
func counter() func() int {
    count := 0
    return func() int {
        count++  // 捕获外部变量 count
        return count
    }
}

inc := counter()
fmt.Println(inc())  // 1
fmt.Println(inc())  // 2
fmt.Println(inc())  // 3

// 每次调用 counter() 都创建独立的 count
inc1 := counter()
inc2 := counter()
fmt.Println(inc1())  // 1
fmt.Println(inc2())  // 1(独立的计数器)

3.2 闭包陷阱

// 经典陷阱:循环中的闭包
func main() {
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i)  // ⚠️ 所有 goroutine 可能都打印 5
        }()
    }
    time.Sleep(time.Second)
}

// 原因:闭包捕获的是变量 i 的引用,循环结束时 i = 5

修复方案:

// 方案1:作为参数传入
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n)  // ✅ 打印 0 1 2 3 4
    }(i)
}

// 方案2:在循环体内创建新变量
for i := 0; i < 5; i++ {
    i := i  // 创建新的局部变量,捕获这个副本
    go func() {
        fmt.Println(i)  // ✅
    }()
}

四、方法(Method)

方法是有接收者(receiver)的函数。接收者决定了方法属于哪个类型。

4.1 定义方法

type Rectangle struct {
    Width  float64
    Height float64
}

// 值接收者:不修改原始值
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 指针接收者:可以修改原始值
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// 使用
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())  // 50

rect.Scale(2)
fmt.Println(rect.Width)  // 20

4.2 值接收者 vs 指针接收者

特性值接收者 (t T)指针接收者 (t *T)
调用时复制一份传入传入引用
修改原始值❌ 不能✅ 可以
性能(大结构体)每次调用复制,可能慢仅传指针,高效
适用场景不修改状态的只读方法需要修改状态的方法

选择规则:

// ✅ 值接收者:只读操作,不修改字段
func (u User) String() string {
    return fmt.Sprintf("%s (%d)", u.Name, u.Age)
}

// ✅ 指针接收者:修改字段或需要避免复制
func (u *User) SetName(name string) {
    u.Name = name
}

// ✅ 指针接收者:结构体较大
func (big *BigStruct) Process() { ... }

惯例:如果类型有任何指针接收者方法,那么所有方法都应该用指针接收者。

4.3 方法值与方法表达式

type Counter struct {
    N int
}

func (c *Counter) Inc() { c.N++ }

// 方法值:绑定到特定接收者
c := &Counter{}
incMethod := c.Inc  // 类型是 func()
incMethod()         // 等价于 c.Inc()

// 方法表达式:类型级别
// IncFunc 的类型是 func(*Counter)
IncFunc := Counter.Inc
IncFunc(&Counter{})  // 传入接收者作为第一个参数

五、函数类型与接口预览

5.1 函数类型

// 定义函数类型
type HandlerFunc func(http.ResponseWriter, *http.Request)

// 使用函数类型
var handler HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

5.2 与接口的关系

// 任何实现了 Error() string 的类型都是 error 接口
type error interface {
    Error() string
}

// 自动满足接口——不需要显式声明
type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Msg)
}

// MyError 自动实现了 error 接口
var err error = &MyError{404, "not found"}

六、内置函数速查

Go 内置了一些不需要导入就能使用的函数:

函数作用示例
len()获取长度(字符串/切片/map/数组)len("hello") → 5
cap()获取容量(切片/数组/channel)cap(s)
make()初始化 slice/map/channelmake([]int, 5)
new()分配内存,返回指针new(int) → *int
append()追加元素到切片append(s, 1)
copy()复制切片copy(dst, src)
delete()删除 map 元素delete(m, key)
close()关闭 channelclose(ch)
panic()触发 panicpanic("fatal")
recover()从 panic 恢复recover()
print()/println()调试用(不推荐)println(x)
complex()/real()/imag()复数操作

七、本节要点速查

概念要点
多返回值(result, error) 是 Go 的核心模式
命名返回值裸 return,短函数中好用
可变参数func(args ...T),调用时 slice...
函数是一等公民可赋值、传参、做返回值
闭包捕获外部变量引用,循环中注意陷阱
值接收者不修改状态,大结构体用指针
指针接收者修改状态,高效传参
内置函数len/cap/make/new/append/copy/delete/close
方法 vs 函数方法属于类型,通过接收者调用

上一篇:05-错误处理 下一篇:07-接口与多态