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/channel | make([]int, 5) |
new() | 分配内存,返回指针 | new(int) → *int |
append() | 追加元素到切片 | append(s, 1) |
copy() | 复制切片 | copy(dst, src) |
delete() | 删除 map 元素 | delete(m, key) |
close() | 关闭 channel | close(ch) |
panic() | 触发 panic | panic("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 函数 | 方法属于类型,通过接收者调用 |
豫公网安备41108202000982号