讲解
函数用 func 关键字定义:func 名字(参数) 返回值 { }。参数类型写在名字后面(和其他很多语言相反),相邻同类型参数可以合并写 a, b int。Go 函数最鲜明的特性是「多返回值」:func divide(a, b float64) (float64, error) 同时返回结果和错误,调用方用两个变量接收——这是 Go 错误处理体系的地基,错误处理章节会全面展开。
可变参数用 ...类型 声明,函数内部它就是一个切片:func sum(nums ...int)。调用时传任意个数的值,或用 切片... 把现有切片展开传入。函数在 Go 里是「一等公民」:可以赋给变量、当参数传、当返回值返回,回调和高阶函数都由此而来。
闭包是「捕获了外层变量的函数」:内层函数引用了外层函数的局部变量,外层函数返回后这些变量仍然活着,跟随闭包继续存在。经典例子是计数器工厂——每次调用工厂得到一个有独立计数的函数。闭包让状态可以优雅地封装在函数里,是 Go 并发编程里 goroutine 捕获上下文变量的基础。
示例
多返回值与 comma ok 风格:
package main
import "fmt"
func divide(a, b float64) (float64, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
func main() {
if q, ok := divide(10, 4); ok {
fmt.Println("商:", q)
}
if _, ok := divide(1, 0); !ok {
fmt.Println("除数不能为零")
}
}
可变参数与切片展开:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3))
fmt.Println(sum()) // 一个都不传也行
s := []int{4, 5, 6}
fmt.Println(sum(s...)) // 切片展开传入
}
闭包:计数器工厂与函数字面量:
package main
import "fmt"
func counter() func() int {
n := 0 // 被闭包捕获,函数返回后依然存活
return func() int {
n++
return n
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next()) // 1 2 3
another := counter() // 每个闭包有独立的 n
fmt.Println(another())
add := func(a, b int) int { return a + b } // 匿名函数赋给变量
fmt.Println("3 + 4 =", add(3, 4))
}
常见坑
- 丢弃错误返回值:q, _ := divide(1, 0) 拿到无意义的 0 还接着用,bug 就此埋下。Go 的多返回值设计就是逼你面对错误,不要用 _ 把 error 吞掉。
- 可变参数与切片混用:sum(nums) 传切片编译不过,要 sum(nums...);反过来函数内把 nums 当切片用是完全合法的。记清「声明 ...、调用 ...、内部是切片」三个位置。
- 命名返回值滥用:func f() (result int) 这类命名返回值配合裸 return 看似省事,超过几行的函数里会让人看不清到底返回了什么。短函数可以用,长函数老实写 return 值。
- 闭包捕获的是变量不是值:循环里起闭包引用循环变量,Go 1.22 前全部捕获同一个变量;1.22 起每次迭代独立。读老代码时注意这个语义分界线。
小结
函数支持多返回值与可变参数,函数值可传递,闭包捕获外层变量实现状态封装。下一节直面很多人最怕的主题:指针。