Go 语言完全指南 / 07 - 函数:多返回值、命名返回、可变参数、闭包、init
07 - 函数
7.1 函数声明
package main
import "fmt"
// 基本函数
func add(a int, b int) int {
return a + b
}
// 相同类型参数可合并
func add2(a, b int) int {
return a + b
}
// 无返回值
func greet(name string) {
fmt.Printf("你好, %s!\n", name)
}
// 无参数无返回值
func main() {
fmt.Println(add(3, 5))
greet("Go")
}
7.2 多返回值
Go 函数可以返回多个值,这是 Go 最重要的特性之一。
package main
import (
"errors"
"fmt"
"math"
)
// 返回商和余数
func divide(a, b int) (int, int, error) {
if b == 0 {
return 0, 0, errors.New("division by zero")
}
return a / b, a % b, nil
}
// 返回值和错误(Go 最常见的模式)
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, errors.New("square root of negative number")
}
return math.Sqrt(x), nil
}
func main() {
// 接收所有返回值
quotient, remainder, err := divide(17, 5)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Printf("17 / 5 = %d 余 %d\n", quotient, remainder)
// 忽略某些返回值
result, _ := sqrt(16)
fmt.Println("√16 =", result)
// 忽略所有返回值
divide(10, 2) // 仅调用,不接收返回值
}
7.3 命名返回值
// 命名返回值
func rectangle(width, height float64) (area, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // 裸返回(naked return),返回命名的值
}
// 等价于
func rectangle2(width, height float64) (float64, float64) {
area := width * height
perimeter := 2 * (width + height)
return area, perimeter
}
func main() {
a, p := rectangle(10, 5)
fmt.Printf("面积: %.1f, 周长: %.1f\n", a, p)
}
⚠️ 注意:命名返回值有助于文档化,但裸返回在长函数中降低可读性。短函数可以使用,长函数建议显式返回。
7.4 可变参数(Variadic)
package main
import "fmt"
// 可变参数函数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 混合固定参数和可变参数
func log(level string, messages ...string) {
for _, msg := range messages {
fmt.Printf("[%s] %s\n", level, msg)
}
}
func main() {
// 直接传递多个参数
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// 传递切片展开
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
// 调用可变参数
log("INFO", "服务器启动", "监听端口 8080")
log("ERROR", "连接失败", "重试中", "超时")
}
7.5 函数作为值(函数类型)
package main
import "fmt"
// 函数类型
type MathFunc func(int, int) int
func add(a, b int) int { return a + b }
func mul(a, b int) int { return a * b }
// 函数作为参数
func apply(f MathFunc, a, b int) int {
return f(a, b)
}
// 函数作为返回值
func getOperator(op string) MathFunc {
switch op {
case "+":
return add
case "*":
return mul
default:
return nil
}
}
// 函数切片
func main() {
// 函数作为变量
var op MathFunc = add
fmt.Println(op(3, 5)) // 8
// 函数作为参数
fmt.Println(apply(add, 3, 5)) // 8
fmt.Println(apply(mul, 3, 5)) // 15
// 函数作为返回值
fn := getOperator("*")
fmt.Println(fn(4, 6)) // 24
// 匿名函数
double := func(x int) int { return x * 2 }
fmt.Println(double(10)) // 20
// 立即调用函数表达式 (IIFE)
result := func(a, b int) int {
return a * b
}(3, 4)
fmt.Println(result) // 12
}
7.6 闭包(Closure)
闭包是引用了外部变量的函数。闭包可以读取和修改其捕获的变量。
package main
import "fmt"
// 计数器工厂
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
// 累加器
func accumulator(initial int) func(int) int {
sum := initial
return func(x int) int {
sum += x
return sum
}
}
// 斐波那契生成器
func fibonacci() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
func main() {
// 独立的计数器
c1 := counter()
c2 := counter()
fmt.Println(c1(), c1(), c1()) // 1, 2, 3
fmt.Println(c2(), c2()) // 1, 2(独立的)
// 累加器
acc := accumulator(100)
fmt.Println(acc(10)) // 110
fmt.Println(acc(20)) // 130
fmt.Println(acc(30)) // 160
// 斐波那契
fib := fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("%d ", fib())
}
fmt.Println() // 0 1 1 2 3 5 8 13 21 34
}
闭包的陷阱
func main() {
// 陷阱:闭包捕获变量的引用
var funcs []func()
for i := 0; i < 5; i++ {
// Go 1.22+ 中 i 每次迭代是新变量,没问题
// Go 1.21- 中需要手动复制
v := i // 复制一份
funcs = append(funcs, func() {
fmt.Println(v)
})
}
for _, f := range funcs {
f()
}
}
7.7 init 函数
init 函数在 main 之前自动执行,用于初始化工作。
package main
import "fmt"
var config map[string]string
func init() {
fmt.Println("init 1: 加载配置")
config = map[string]string{
"db_host": "localhost",
"db_port": "5432",
}
}
func init() {
fmt.Println("init 2: 验证配置")
if config["db_host"] == "" {
panic("db_host is required")
}
}
func main() {
fmt.Println("main: 程序启动")
fmt.Printf("配置: %+v\n", config)
}
// 输出顺序:
// init 1: 加载配置
// init 2: 验证配置
// main: 程序启动
init 的执行顺序
1. 包级变量初始化(按声明顺序,依赖优先)
2. 包的 init 函数(按出现顺序)
3. main 包的 init 函数
4. main 函数
// a/a.go
package a
import "fmt"
var A = initA()
func initA() string {
fmt.Println("a: 变量初始化")
return "A"
}
func init() {
fmt.Println("a: init 函数")
}
// main.go
package main
import (
"fmt"
_ "myproject/a" // 导入但不使用
)
func main() {
fmt.Println("main")
}
// 输出:
// a: 变量初始化
// a: init 函数
// main
init 的实际用途
// 数据库驱动注册
import _ "github.com/go-sql-driver/mysql"
// 正则表达式预编译
var emailRegex *regexp.Regexp
func init() {
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
}
// 验证必需的环境变量
func init() {
required := []string{"DATABASE_URL", "API_KEY"}
for _, env := range required {
if os.Getenv(env) == "" {
log.Fatalf("环境变量 %s 未设置", env)
}
}
}
⚠️ 注意:尽量减少 init 的使用。显式初始化比隐式更清晰、更易测试。
7.8 递归
package main
import "fmt"
// 阶乘
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
// 斐波那契
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
// 尾递归优化(Go 不保证 TCO,但可以用累加器模拟)
func factorialTail(n, acc int) int {
if n <= 1 {
return acc
}
return factorialTail(n-1, n*acc)
}
// 二叉树遍历
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func inorder(root *TreeNode) []int {
if root == nil {
return nil
}
var result []int
result = append(result, inorder(root.Left)...)
result = append(result, root.Val)
result = append(result, inorder(root.Right)...)
return result
}
func main() {
fmt.Println("5! =", factorial(5)) // 120
fmt.Println("fib(10) =", fib(10)) // 55
fmt.Println("5! (tail) =", factorialTail(5, 1)) // 120
// 构建二叉树
root := &TreeNode{Val: 4,
Left: &TreeNode{Val: 2,
Left: &TreeNode{Val: 1},
Right: &TreeNode{Val: 3},
},
Right: &TreeNode{Val: 6,
Left: &TreeNode{Val: 5},
Right: &TreeNode{Val: 7},
},
}
fmt.Println("中序遍历:", inorder(root)) // [1 2 3 4 5 6 7]
}
⚠️ 注意:Go 不保证尾递归优化。深递归可能导致栈溢出。大深度时考虑改用迭代。
7.9 方法(Method)
package main
import (
"fmt"
"math"
)
type Point struct {
X, Y float64
}
// 值接收者方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// 指针接收者方法(可修改接收者)
func (p *Point) Translate(dx, dy float64) {
p.X += dx
p.Y += dy
}
// 值接收者(不影响原值)
func (p Point) String() string {
return fmt.Sprintf("(%.1f, %.1f)", p.X, p.Y)
}
func main() {
p1 := Point{0, 0}
p2 := Point{3, 4}
fmt.Printf("距离: %.1f\n", p1.Distance(p2)) // 5.0
p1.Translate(10, 20)
fmt.Println("平移后:", p1) // (10.0, 20.0)
}
| 接收者类型 | 可调用方法类型 | 使用场景 |
|---|---|---|
| 值 (T) | 值方法和指针方法 | 不修改接收者 |
| 指针 (*T) | 值方法和指针方法 | 需要修改接收者、大结构体避免拷贝 |
7.10 defer 与函数配合
func readFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close() // 确保资源释放
return io.ReadAll(f)
}
// 多 defer 常见模式
func transaction(db *sql.DB) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // 如果 Commit 了,Rollback 是空操作
// 执行操作...
if err := tx.Exec("INSERT ..."); err != nil {
return err
}
return tx.Commit() // Commit 成功后,Rollback 不再执行
}
🏢 业务场景
- 错误处理模式:函数返回
(result, error)是 Go 的标准错误处理模式 - 中间件链:函数作为参数/返回值,实现 HTTP 中间件
- 策略模式:用函数类型实现算法策略选择
- 连接池初始化:init 函数注册数据库驱动
- 事件回调:闭包作为回调函数捕获上下文