主题
第04章 函数 —— 程序的"乐高积木"
本章目标:掌握 Go 函数的多返回值、可变参数、命名返回、闭包、defer,以及函数作为一等公民的玩法。
1. 为什么要写函数?
🌰 生活化比喻:写程序就像做料理。如果每次做饭都从切菜开始,效率太低。我们应该把"切菜"封装成一个函数,需要时调用。
函数 = 把一段做某件事的代码起个名字,下次直接喊它干活。
2. 函数声明
2.1 基本语法
go
func add(a int, b int) int {
return a + b
}结构:func 函数名(参数列表) 返回值类型 { 函数体 }
2.2 简写:相同类型的参数
go
func add(a, b int) int { // a 和 b 都是 int
return a + b
}2.3 多返回值:Go 的招牌特性
go
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为 0")
}
return a / b, nil
}
result, err := divide(10, 0)🌰 生活化比喻:相当于一个快递员同时给你送两个包裹。Go 鼓励用第二个返回值返回错误,这就是 error first 哲学。
2.4 命名返回值
go
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 自动返回 x, y(裸返回)
}⚠️ 慎用裸返回——函数太长时可读性差。
📊 配套图解:diagrams/return-types.svg
3. 可变参数
go
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15
sum([]int{10, 20, 30}...) // 60(注意 ... 展开)...int 在函数内会变成 []int。这是 fmt.Println 能接收任意多个参数的奥秘。
4. 函数是一等公民
Go 的函数可以:
- 赋值给变量
- 作为参数传递
- 作为返回值
go
// 赋值
add := func(a, b int) int { return a + b }
fmt.Println(add(2, 3))
// 作为参数
nums := []int{1, 2, 3, 4}
sum := reduce(nums, func(acc, n int) int { return acc + n }, 0)
// 返回函数
func multiplier(factor int) func(int) int {
return func(n int) int { return n * factor }
}
double := multiplier(2)
fmt.Println(double(5)) // 105. 闭包:函数能"记住"外部变量
go
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3🌰 生活化比喻:闭包像带秘书的总裁——总裁(返回的函数)出门办事,秘书(被引用的变量)跟着他记笔记,外人看不到笔记本。
📊 配套图解:diagrams/closure.svg
6. defer:Go 的"善后特工"
defer 把语句推迟到当前函数返回前执行。
go
func readFile(name string) {
f, _ := os.Open(name)
defer f.Close() // 函数退出前自动关闭文件
// ... 读文件 ...
}6.1 多个 defer:后进先出(LIFO)
go
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// 输出顺序:3 2 1(像栈)🌰 生活化比喻:你给三个人交代"我走之前你们各做一件事",但是按交代的反序执行,就像把要做的事写在便签纸上叠成一摞——最后贴的最先撕。
6.2 defer 的"取值时机"
go
func tricky() {
x := 10
defer fmt.Println("defer:", x) // 这里 x 是 10 (实参立即求值)
x = 20
fmt.Println("now:", x) // 20
}
// 输出:
// now: 20
// defer: 10⚠️ defer 的参数在 defer 那一行就已被求值,但函数体到结尾才执行。
但闭包形式不一样:
go
func tricky2() {
x := 10
defer func() { fmt.Println("defer:", x) }() // 闭包,x 是引用
x = 20
}
// 输出:defer: 20📊 配套图解:diagrams/defer-stack.svg
6.3 defer 的常见用途
| 场景 | 写法 |
|---|---|
| 关闭文件 | defer f.Close() |
| 释放锁 | defer mu.Unlock() |
| 关闭 channel | defer close(ch) |
| 关闭网络连接 | defer conn.Close() |
| recover panic | defer func(){ recover() }() |
| 性能计时 | defer trace("xxx")() |
7. 递归函数
go
func factorial(n int) int {
if n <= 1 { return 1 }
return n * factorial(n-1)
}⚠️ Go 没有"尾递归优化",深度太大会栈溢出。深度 > 1 万要改循环。
8. 案例:函数工厂 + 中间件
go
// 一个简单的中间件思路:函数包装函数
type Handler func(string) string
func logMiddleware(next Handler) Handler {
return func(req string) string {
fmt.Println("[LOG] in:", req)
resp := next(req)
fmt.Println("[LOG] out:", resp)
return resp
}
}
func handler(req string) string {
return "Hello, " + req
}
wrapped := logMiddleware(handler)
wrapped("Tom")
// [LOG] in: Tom
// [LOG] out: Hello, Tom这就是 Web 框架的中间件雏形。
9. 本章小结
✅ Go 函数支持多返回值,常见 (result, error) 模式
✅ 同类型参数可以共享:func f(a, b int)
✅ 可变参数 ...int 在函数内是 []int
✅ 函数是一等公民:可赋值、可传参、可返回
✅ 闭包让函数"携带"外部变量
✅ defer 在函数返回前执行,多个 defer 是 LIFO 顺序
✅ defer 的参数立即求值,但函数体延迟执行
📂 配套资源
- examples/01_basic.go —— 基本声明、多返回值
- examples/02_variadic.go —— 可变参数
- examples/03_first_class.go —— 函数作为值
- examples/04_closure.go —— 闭包
- examples/05_defer.go —— defer 全解
- demo.html —— 浏览器演示
- interview.md —— 面试题